diff --git a/README.md b/README.md index 419eaec..29ad1d4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + +[](https://github.com/jauntsdn/netty-websocket-http1/actions/workflows/ci-build.yml) + # netty-websocket-http1 Alternative Netty implementation of [RFC6455](https://tools.ietf.org/html/rfc6455) - the WebSocket protocol. @@ -59,9 +62,10 @@ non-masked frames with 8, 64, 125, 1000 bytes of payload over encrypted/non-encr ### websocket-http2 -Library may be combined with [jauntsdn/websocket-http2](https://github.com/jauntsdn/netty-websocket-http2) using [http1 codec](https://github.com/jauntsdn/netty-websocket-http2/blob/develop/netty-websocket-http2/src/main/java/com/jauntsdn/netty/handler/codec/http2/websocketx/Http1WebSocketCodec.java) +Library may be combined with [jauntsdn/websocket-http2](https://github.com/jauntsdn/netty-websocket-http2) using [http1 codec](https://github.com/jauntsdn/netty-websocket-http2/blob/develop/netty-websocket-http2-callbacks-codec/src/main/java/com/jauntsdn/netty/handler/codec/http2/websocketx/WebSocketCallbacksCodec.java) -for significantly improved per-core throughput [this codec perf-test](), [netty built-in codec perf-test](): +for significantly improved per-core throughput [this codec perf-test](https://github.com/jauntsdn/netty-websocket-http2/tree/develop/netty-websocket-http2-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http2/websocketx/perftest/callbackscodec), +[netty built-in codec perf-test](https://github.com/jauntsdn/netty-websocket-http2/tree/develop/netty-websocket-http2-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http2/websocketx/perftest/messagecodec): for 8, 125, 1000 byte payload frames over encrypted connection results are as follows: | payload size | this codec, million msgs | netty's codec, million msgs | @@ -162,7 +166,7 @@ repositories { } dependencies { - implementation "com.jauntsdn.netty:netty-websocket-http1:<VERSION>" + implementation "com.jauntsdn.netty:netty-websocket-http1:0.9.0" } ``` diff --git a/gradle.properties b/gradle.properties index 5ba75a4..ec516f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.jauntsdn.netty -version=0.9.0 +version=0.9.1 googleJavaFormatPluginVersion=0.9 dependencyManagementPluginVersion=1.1.0 @@ -7,15 +7,15 @@ gitPluginVersion=0.13.0 osDetectorPluginVersion=1.7.1 versionsPluginVersion=0.44.0 -nettyVersion=4.1.86.Final -nettyTcnativeVersion=2.0.54.Final +nettyVersion=4.1.87.Final +nettyTcnativeVersion=2.0.56.Final hdrHistogramVersion=2.1.12 slf4jVersion=1.7.36 logbackVersion=1.2.11 jsr305Version=3.0.2 -junitVersion=5.9.1 -assertjVersion=3.23.1 +junitVersion=5.9.2 +assertjVersion=3.24.2 org.gradle.parallel=true org.gradle.configureondemand=true \ No newline at end of file diff --git a/license.md b/license.md new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/license.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/netty-websocket-http1-soaktest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/soaktest/server/Main.java b/netty-websocket-http1-soaktest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/soaktest/server/Main.java index bd09017..dbfe6e8 100644 --- a/netty-websocket-http1-soaktest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/soaktest/server/Main.java +++ b/netty-websocket-http1-soaktest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/soaktest/server/Main.java @@ -51,6 +51,9 @@ public static void main(String[] args) throws Exception { String host = System.getProperty("HOST", "localhost"); int port = Integer.parseInt(System.getProperty("PORT", "8088")); + int frameSizeLimit = Integer.parseInt(System.getProperty("SIZE", "65535")); + boolean expectMasked = Boolean.parseBoolean(System.getProperty("MASKED", "false")); + boolean maskMismatch = !Boolean.parseBoolean(System.getProperty("STRICT", "false")); boolean isOpensslAvailable = OpenSsl.isAvailable(); boolean isEpollAvailable = Transport.isEpollAvailable(); @@ -71,7 +74,8 @@ public static void main(String[] args) throws Exception { bootstrap .group(transport.eventLoopGroup()) .channel(transport.serverChannel()) - .childHandler(new ConnectionAcceptor(sslContext)) + .childHandler( + new ConnectionAcceptor(sslContext, frameSizeLimit, expectMasked, maskMismatch)) .bind(host, port) .sync() .channel(); @@ -83,13 +87,17 @@ private static class ConnectionAcceptor extends ChannelInitializer<SocketChannel private final SslContext sslContext; private final WebSocketDecoderConfig webSocketDecoderConfig; - ConnectionAcceptor(SslContext sslContext) { + ConnectionAcceptor( + SslContext sslContext, + int frameSizeLimit, + boolean expectMasked, + boolean allowMaskMismatch) { this.sslContext = sslContext; this.webSocketDecoderConfig = WebSocketDecoderConfig.newBuilder() - .allowMaskMismatch(true) - .expectMaskedFrames(false) - .maxFramePayloadLength(65_535) + .allowMaskMismatch(allowMaskMismatch) + .expectMaskedFrames(expectMasked) + .maxFramePayloadLength(frameSizeLimit) .withUTF8Validator(false) .build(); } diff --git a/netty-websocket-http1-test/gradle.lockfile b/netty-websocket-http1-test/gradle.lockfile index de991d9..e4452cb 100644 --- a/netty-websocket-http1-test/gradle.lockfile +++ b/netty-websocket-http1-test/gradle.lockfile @@ -9,26 +9,26 @@ com.google.errorprone:javac-shaded:9+181-r4173-1=googleJavaFormat1.6 com.google.googlejavaformat:google-java-format:1.6=googleJavaFormat1.6 com.google.guava:guava:22.0=googleJavaFormat1.6 com.google.j2objc:j2objc-annotations:1.1=googleJavaFormat1.6 -io.netty:netty-buffer:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec-http:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-codec:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-common:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-handler:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-resolver:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-kqueue:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-transport:4.1.86.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.12.10=testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec-http:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-codec:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-common:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-handler:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-resolver:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-epoll:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-kqueue:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-transport:4.1.87.Final=compileClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.12.21=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath -org.assertj:assertj-core:3.23.1=testCompileClasspath,testRuntimeClasspath +org.assertj:assertj-core:3.24.2=testCompileClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.14=googleJavaFormat1.6 -org.junit.jupiter:junit-jupiter-api:5.9.1=testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:5.9.1=testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:5.9.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:1.9.1=testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:1.9.1=testRuntimeClasspath -org.junit:junit-bom:5.9.1=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.9.2=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.9.2=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.9.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.9.2=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.9.2=testRuntimeClasspath +org.junit:junit-bom:5.9.2=testCompileClasspath,testRuntimeClasspath org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testRuntimeClasspath org.slf4j:slf4j-api:1.7.36=compileClasspath,testCompileClasspath,testRuntimeClasspath empty=annotationProcessor,testAnnotationProcessor diff --git a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java index 116550d..d534e8c 100644 --- a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java +++ b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java @@ -21,6 +21,7 @@ import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.CompositeByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; @@ -58,6 +59,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,6 +79,25 @@ void tearDown() { } } + @Timeout(300) + @ValueSource(booleans = {true, false}) + @ParameterizedTest + void binaryFramesEncoder(boolean mask) throws Exception { + int maxFrameSize = DEFAULT_CODEC_MAX_FRAME_SIZE; + Channel s = server = nettyServer(new BinaryFramesTestServerHandler(), mask, false); + BinaryFramesEncoderClientHandler clientHandler = + new BinaryFramesEncoderClientHandler(maxFrameSize); + Channel client = + webSocketCallbacksClient(s.localAddress(), mask, true, maxFrameSize, clientHandler); + + WebSocketFrameFactory.Encoder encoder = clientHandler.onHandshakeCompleted().join(); + Assertions.assertThat(encoder).isNotNull(); + + CompletableFuture<Void> onComplete = clientHandler.startFramesExchange(); + onComplete.join(); + client.close(); + } + @Timeout(300) @MethodSource("maskingArgs") @ParameterizedTest @@ -423,6 +444,155 @@ protected void initChannel(SocketChannel ch) { } } + static class BinaryFramesEncoderClientHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + private final CompletableFuture<WebSocketFrameFactory.Encoder> onHandshakeComplete = + new CompletableFuture<>(); + private final CompletableFuture<Void> onFrameExchangeComplete = new CompletableFuture<>(); + private WebSocketFrameFactory.Encoder binaryFrameEncoder; + private final int framesCount; + private int receivedFrames; + private int sentFrames; + private volatile ChannelHandlerContext ctx; + + BinaryFramesEncoderClientHandler(int maxFrameSize) { + this.framesCount = maxFrameSize; + } + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory) { + this.binaryFrameEncoder = webSocketFrameFactory.encoder(); + return this; + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (!finalFragment) { + onFrameExchangeComplete.completeExceptionally( + new AssertionError("received non-final frame: " + finalFragment)); + payload.release(); + return; + } + if (rsv != 0) { + onFrameExchangeComplete.completeExceptionally( + new AssertionError("received frame with non-zero rsv: " + rsv)); + payload.release(); + return; + } + if (opcode != WebSocketProtocol.OPCODE_BINARY) { + onFrameExchangeComplete.completeExceptionally( + new AssertionError("received non-binary frame: " + Long.toHexString(opcode))); + payload.release(); + return; + } + + int readableBytes = payload.readableBytes(); + + int expectedSize = receivedFrames; + if (expectedSize != readableBytes) { + onFrameExchangeComplete.completeExceptionally( + new AssertionError( + "received frame of unexpected size: " + + expectedSize + + ", actual: " + + readableBytes)); + payload.release(); + return; + } + + for (int i = 0; i < readableBytes; i++) { + byte b = payload.readByte(); + if (b != (byte) 0xFE) { + onFrameExchangeComplete.completeExceptionally( + new AssertionError("received frame with unexpected content: " + Long.toHexString(b))); + payload.release(); + return; + } + } + payload.release(); + if (++receivedFrames == framesCount) { + onFrameExchangeComplete.complete(null); + } + } + + @Override + public void onChannelWritabilityChanged(ChannelHandlerContext ctx) { + boolean writable = ctx.channel().isWritable(); + if (sentFrames > 0 && writable) { + int toSend = framesCount - sentFrames; + if (toSend > 0) { + sendFrames(ctx, toSend); + } + } + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + this.ctx = ctx; + onHandshakeComplete.complete(binaryFrameEncoder); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + if (!onFrameExchangeComplete.isDone()) { + onFrameExchangeComplete.completeExceptionally(new ClosedChannelException()); + } + } + + @Override + public void onExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + if (!onFrameExchangeComplete.isDone()) { + onFrameExchangeComplete.completeExceptionally(cause); + } + } + + CompletableFuture<WebSocketFrameFactory.Encoder> onHandshakeCompleted() { + return onHandshakeComplete; + } + + CompletableFuture<Void> startFramesExchange() { + ChannelHandlerContext c = ctx; + c.executor().execute(() -> sendFrames(c, framesCount - sentFrames)); + return onFrameExchangeComplete; + } + + private void sendFrames(ChannelHandlerContext c, int toSend) { + Channel ch = c.channel(); + WebSocketFrameFactory.Encoder frameEncoder = binaryFrameEncoder; + boolean pendingFlush = false; + ByteBufAllocator allocator = c.alloc(); + for (int frameIdx = 0; frameIdx < toSend; frameIdx++) { + if (!c.channel().isOpen()) { + return; + } + int payloadSize = sentFrames; + int frameSize = frameEncoder.sizeofBinaryFrame(payloadSize); + ByteBuf binaryFrame = allocator.buffer(frameSize); + binaryFrame.writerIndex(frameSize - payloadSize); + for (int payloadIdx = 0; payloadIdx < payloadSize; payloadIdx++) { + binaryFrame.writeByte(0xFE); + } + ByteBuf maskedBinaryFrame = frameEncoder.encodeBinaryFrame(binaryFrame); + sentFrames++; + if (ch.bytesBeforeUnwritable() < binaryFrame.capacity()) { + c.writeAndFlush(maskedBinaryFrame, c.voidPromise()); + pendingFlush = false; + if (!ch.isWritable()) { + return; + } + } else { + c.write(maskedBinaryFrame, c.voidPromise()); + pendingFlush = true; + } + } + if (pendingFlush) { + c.flush(); + } + } + } + static class BinaryFramesTestClientHandler implements WebSocketCallbacksHandler, WebSocketFrameListener { private final CompletableFuture<WebSocketFrameFactory> onHandshakeComplete = diff --git a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketValidationTest.java b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketValidationTest.java index 29099a1..877753d 100644 --- a/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketValidationTest.java +++ b/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketValidationTest.java @@ -36,6 +36,8 @@ import io.netty.util.ReferenceCountUtil; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -44,6 +46,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class WebSocketValidationTest { Channel server; @@ -59,7 +63,7 @@ void tearDown() throws Exception { @Timeout(15) @Test void frameSizeLimit() throws Exception { - FrameSizeLimitServerHandler serverHandler = new FrameSizeLimitServerHandler(); + ValidationTestServerHandler serverHandler = new ValidationTestServerHandler(); Channel s = server = testServer("localhost", 0, decoderConfig(125), serverHandler); FrameSizeLimitClientHandler clientHandler = new FrameSizeLimitClientHandler(126); Channel client = testClient(s.localAddress(), 125, clientHandler); @@ -86,20 +90,161 @@ void frameSizeLimit() throws Exception { } } - @Test - void controlFrameSizeLimit() {} + @Timeout(15) + @ValueSource( + bytes = { + WebSocketProtocol.OPCODE_PING, + WebSocketProtocol.OPCODE_PONG, + WebSocketProtocol.OPCODE_CLOSE + }) + @ParameterizedTest + void controlFrameSizeLimit(byte opcode) throws Exception { + ValidationTestServerHandler serverHandler = new ValidationTestServerHandler(); + Channel s = server = testServer("localhost", 0, decoderConfig(65_535), serverHandler); + ControlFrameSizeLimitClientHandler clientHandler = + new ControlFrameSizeLimitClientHandler(opcode); + Channel client = testClient(s.localAddress(), 65_535, clientHandler); + serverHandler.onClose.join(); + clientHandler.onClose.join(); + + Throwable serverInboundException = serverHandler.inboundException; + Assertions.assertThat(serverInboundException).isNotNull(); + Assertions.assertThat(serverInboundException) + .isInstanceOf(CorruptedWebSocketFrameException.class); + WebSocketCloseStatus closeStatus = + ((CorruptedWebSocketFrameException) serverInboundException).closeStatus(); + Assertions.assertThat(closeStatus.code()) + .isEqualTo(WebSocketCloseStatus.MESSAGE_TOO_BIG.code()); + Assertions.assertThat(serverHandler.framesReceived).isEqualTo(0); + Assertions.assertThat(clientHandler.nonCloseFrames).isEqualTo(0); + Set<ByteBuf> closeFrames = clientHandler.closeFrames; + try { + Assertions.assertThat(closeFrames.size()).isEqualTo(1); + ByteBuf closeFramePayload = closeFrames.iterator().next(); + Assertions.assertThat(WebSocketFrameListener.CloseFramePayload.statusCode(closeFramePayload)) + .isEqualTo(WebSocketCloseStatus.MESSAGE_TOO_BIG.code()); + } finally { + closeFrames.forEach(ByteBuf::release); + } + } + @Timeout(15) @Test - void frameWithExtensions() {} + void frameWithExtensions() throws Exception { + ValidationTestServerHandler serverHandler = new ValidationTestServerHandler(); + Channel s = server = testServer("localhost", 0, decoderConfig(65_535), serverHandler); + ExtensionFrameClientHandler clientHandler = new ExtensionFrameClientHandler(); + Channel client = testClient(s.localAddress(), 65_535, clientHandler); + serverHandler.onClose.join(); + clientHandler.onClose.join(); + Throwable serverInboundException = serverHandler.inboundException; + Assertions.assertThat(serverInboundException).isNotNull(); + Assertions.assertThat(serverInboundException) + .isInstanceOf(CorruptedWebSocketFrameException.class); + WebSocketCloseStatus closeStatus = + ((CorruptedWebSocketFrameException) serverInboundException).closeStatus(); + Assertions.assertThat(closeStatus.code()).isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + Assertions.assertThat(serverHandler.framesReceived).isEqualTo(0); + Assertions.assertThat(clientHandler.nonCloseFrames).isEqualTo(0); + Set<ByteBuf> closeFrames = clientHandler.closeFrames; + try { + Assertions.assertThat(closeFrames.size()).isEqualTo(1); + ByteBuf closeFramePayload = closeFrames.iterator().next(); + Assertions.assertThat(WebSocketFrameListener.CloseFramePayload.statusCode(closeFramePayload)) + .isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + } finally { + closeFrames.forEach(ByteBuf::release); + } + } + + @Timeout(15) @Test - void invalidFragmentStart() {} + void invalidFragmentStart() throws Exception { + ValidationTestServerHandler serverHandler = new ValidationTestServerHandler(); + Channel s = server = testServer("localhost", 0, decoderConfig(65_535), serverHandler); + FragmentStartClientHandler clientHandler = new FragmentStartClientHandler(); + Channel client = testClient(s.localAddress(), 65_535, clientHandler); + serverHandler.onClose.join(); + clientHandler.onClose.join(); + + Throwable serverInboundException = serverHandler.inboundException; + Assertions.assertThat(serverInboundException).isNotNull(); + Assertions.assertThat(serverInboundException) + .isInstanceOf(CorruptedWebSocketFrameException.class); + WebSocketCloseStatus closeStatus = + ((CorruptedWebSocketFrameException) serverInboundException).closeStatus(); + Assertions.assertThat(closeStatus.code()).isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + Assertions.assertThat(serverHandler.framesReceived).isEqualTo(0); + Assertions.assertThat(clientHandler.nonCloseFrames).isEqualTo(0); + Set<ByteBuf> closeFrames = clientHandler.closeFrames; + try { + Assertions.assertThat(closeFrames.size()).isEqualTo(1); + ByteBuf closeFramePayload = closeFrames.iterator().next(); + Assertions.assertThat(WebSocketFrameListener.CloseFramePayload.statusCode(closeFramePayload)) + .isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + } finally { + closeFrames.forEach(ByteBuf::release); + } + } @Test - void invalidFragmentContinuation() {} + void invalidFragmentContinuation() throws Exception { + ValidationTestServerHandler serverHandler = new ValidationTestServerHandler(); + Channel s = server = testServer("localhost", 0, decoderConfig(65_535), serverHandler); + FragmentContinuationClientHandler clientHandler = new FragmentContinuationClientHandler(); + Channel client = testClient(s.localAddress(), 65_535, clientHandler); + serverHandler.onClose.join(); + clientHandler.onClose.join(); + + Throwable serverInboundException = serverHandler.inboundException; + Assertions.assertThat(serverInboundException).isNotNull(); + Assertions.assertThat(serverInboundException) + .isInstanceOf(CorruptedWebSocketFrameException.class); + WebSocketCloseStatus closeStatus = + ((CorruptedWebSocketFrameException) serverInboundException).closeStatus(); + Assertions.assertThat(closeStatus.code()).isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + Assertions.assertThat(serverHandler.framesReceived).isEqualTo(1); + Assertions.assertThat(clientHandler.nonCloseFrames).isEqualTo(0); + Set<ByteBuf> closeFrames = clientHandler.closeFrames; + try { + Assertions.assertThat(closeFrames.size()).isEqualTo(1); + ByteBuf closeFramePayload = closeFrames.iterator().next(); + Assertions.assertThat(WebSocketFrameListener.CloseFramePayload.statusCode(closeFramePayload)) + .isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + } finally { + closeFrames.forEach(ByteBuf::release); + } + } @Test - void invalidFragmentCompletion() {} + void invalidFragmentCompletion() throws Exception { + ValidationTestServerHandler serverHandler = new ValidationTestServerHandler(); + Channel s = server = testServer("localhost", 0, decoderConfig(65_535), serverHandler); + FragmentCompletionClientHandler clientHandler = new FragmentCompletionClientHandler(); + Channel client = testClient(s.localAddress(), 65_535, clientHandler); + serverHandler.onClose.join(); + clientHandler.onClose.join(); + + Throwable serverInboundException = serverHandler.inboundException; + Assertions.assertThat(serverInboundException).isNotNull(); + Assertions.assertThat(serverInboundException) + .isInstanceOf(CorruptedWebSocketFrameException.class); + WebSocketCloseStatus closeStatus = + ((CorruptedWebSocketFrameException) serverInboundException).closeStatus(); + Assertions.assertThat(closeStatus.code()).isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + Assertions.assertThat(serverHandler.framesReceived).isEqualTo(1); + Assertions.assertThat(clientHandler.nonCloseFrames).isEqualTo(0); + Set<ByteBuf> closeFrames = clientHandler.closeFrames; + try { + Assertions.assertThat(closeFrames.size()).isEqualTo(1); + ByteBuf closeFramePayload = closeFrames.iterator().next(); + Assertions.assertThat(WebSocketFrameListener.CloseFramePayload.statusCode(closeFramePayload)) + .isEqualTo(WebSocketCloseStatus.PROTOCOL_ERROR.code()); + } finally { + closeFrames.forEach(ByteBuf::release); + } + } static WebSocketDecoderConfig decoderConfig(int maxFramePayloadLength) { return WebSocketDecoderConfig.newBuilder() @@ -148,6 +293,249 @@ protected void initChannel(SocketChannel ch) { .channel(); } + static class ExtensionFrameClientHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + public static final int PAYLOAD_SIZE = 42; + final CompletableFuture<Void> onClose = new CompletableFuture<>(); + final Set<ByteBuf> closeFrames = ConcurrentHashMap.newKeySet(); + volatile int nonCloseFrames; + WebSocketFrameFactory webSocketFrameFactory; + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory) { + this.webSocketFrameFactory = webSocketFrameFactory; + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + WebSocketFrameFactory factory = webSocketFrameFactory; + ByteBuf controlFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + controlFrame.setByte(0, controlFrame.getByte(0) | 0b0100_0000); + byte[] payloadBytes = new byte[PAYLOAD_SIZE]; + ThreadLocalRandom.current().nextBytes(payloadBytes); + controlFrame.writeBytes(payloadBytes); + ctx.writeAndFlush(factory.mask(controlFrame)); + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode == WebSocketProtocol.OPCODE_CLOSE) { + closeFrames.add(payload); + return; + } + //noinspection NonAtomicOperationOnVolatileField: written from single thread + nonCloseFrames++; + ReferenceCountUtil.release(payload); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + onClose.complete(null); + } + } + + static class FragmentStartClientHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + public static final int PAYLOAD_SIZE = 127; + final CompletableFuture<Void> onClose = new CompletableFuture<>(); + final Set<ByteBuf> closeFrames = ConcurrentHashMap.newKeySet(); + volatile int nonCloseFrames; + WebSocketFrameFactory webSocketFrameFactory; + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory) { + this.webSocketFrameFactory = webSocketFrameFactory; + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + WebSocketFrameFactory factory = webSocketFrameFactory; + ByteBuf fragmentFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + fragmentFrame.setByte(0, 0); + byte[] payloadBytes = new byte[PAYLOAD_SIZE]; + ThreadLocalRandom.current().nextBytes(payloadBytes); + fragmentFrame.writeBytes(payloadBytes); + ctx.writeAndFlush(factory.mask(fragmentFrame)); + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode == WebSocketProtocol.OPCODE_CLOSE) { + closeFrames.add(payload); + return; + } + //noinspection NonAtomicOperationOnVolatileField: written from single thread + nonCloseFrames++; + ReferenceCountUtil.release(payload); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + onClose.complete(null); + } + } + + static class FragmentContinuationClientHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + public static final int PAYLOAD_SIZE = 127; + final CompletableFuture<Void> onClose = new CompletableFuture<>(); + final Set<ByteBuf> closeFrames = ConcurrentHashMap.newKeySet(); + volatile int nonCloseFrames; + WebSocketFrameFactory webSocketFrameFactory; + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory) { + this.webSocketFrameFactory = webSocketFrameFactory; + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + byte[] payloadBytes = new byte[PAYLOAD_SIZE]; + ThreadLocalRandom.current().nextBytes(payloadBytes); + WebSocketFrameFactory factory = webSocketFrameFactory; + + ByteBuf fragmentStartFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + fragmentStartFrame.setByte(0, fragmentStartFrame.getByte(0) & 0b0111_1111); + fragmentStartFrame.writeBytes(payloadBytes); + ctx.write(factory.mask(fragmentStartFrame)); + + ByteBuf fragmentContFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + fragmentContFrame.setByte(0, fragmentContFrame.getByte(0) & 0b0111_1111); + fragmentContFrame.writeBytes(payloadBytes); + ctx.writeAndFlush(factory.mask(fragmentContFrame)); + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode == WebSocketProtocol.OPCODE_CLOSE) { + closeFrames.add(payload); + return; + } + //noinspection NonAtomicOperationOnVolatileField: written from single thread + nonCloseFrames++; + ReferenceCountUtil.release(payload); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + onClose.complete(null); + } + } + + static class FragmentCompletionClientHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + public static final int PAYLOAD_SIZE = 127; + final CompletableFuture<Void> onClose = new CompletableFuture<>(); + final Set<ByteBuf> closeFrames = ConcurrentHashMap.newKeySet(); + volatile int nonCloseFrames; + WebSocketFrameFactory webSocketFrameFactory; + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory) { + this.webSocketFrameFactory = webSocketFrameFactory; + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + byte[] payloadBytes = new byte[PAYLOAD_SIZE]; + ThreadLocalRandom.current().nextBytes(payloadBytes); + WebSocketFrameFactory factory = webSocketFrameFactory; + + ByteBuf fragmentStartFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + fragmentStartFrame.setByte(0, fragmentStartFrame.getByte(0) & 0b0111_1111); + fragmentStartFrame.writeBytes(payloadBytes); + ctx.write(factory.mask(fragmentStartFrame)); + + ByteBuf fragmentContFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + fragmentContFrame.writeBytes(payloadBytes); + ctx.writeAndFlush(factory.mask(fragmentContFrame)); + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode == WebSocketProtocol.OPCODE_CLOSE) { + closeFrames.add(payload); + return; + } + //noinspection NonAtomicOperationOnVolatileField: written from single thread + nonCloseFrames++; + ReferenceCountUtil.release(payload); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + onClose.complete(null); + } + } + + static class ControlFrameSizeLimitClientHandler + implements WebSocketCallbacksHandler, WebSocketFrameListener { + public static final int PAYLOAD_SIZE = 127; + final CompletableFuture<Void> onClose = new CompletableFuture<>(); + final Set<ByteBuf> closeFrames = ConcurrentHashMap.newKeySet(); + final byte opcode; + volatile int nonCloseFrames; + WebSocketFrameFactory webSocketFrameFactory; + + ControlFrameSizeLimitClientHandler(byte opcode) { + this.opcode = opcode; + } + + @Override + public WebSocketFrameListener exchange( + ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory) { + this.webSocketFrameFactory = webSocketFrameFactory; + return this; + } + + @Override + public void onOpen(ChannelHandlerContext ctx) { + WebSocketFrameFactory factory = webSocketFrameFactory; + ByteBuf controlFrame = factory.createBinaryFrame(ctx.alloc(), PAYLOAD_SIZE); + controlFrame.setByte(0, controlFrame.getByte(0) & 0xF0 | opcode); + if (opcode == WebSocketProtocol.OPCODE_CLOSE) { + String closeMsg = String.join("", Collections.nCopies(25, "close")); + controlFrame + .writeShort(WebSocketCloseStatus.NORMAL_CLOSURE.code()) + .writeCharSequence(closeMsg, StandardCharsets.UTF_8); + } else { + byte[] payloadBytes = new byte[PAYLOAD_SIZE]; + ThreadLocalRandom.current().nextBytes(payloadBytes); + controlFrame.writeBytes(payloadBytes); + } + ctx.writeAndFlush(factory.mask(controlFrame)); + } + + @Override + public void onChannelRead( + ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + if (opcode == WebSocketProtocol.OPCODE_CLOSE) { + closeFrames.add(payload); + return; + } + //noinspection NonAtomicOperationOnVolatileField: written from single thread + nonCloseFrames++; + ReferenceCountUtil.release(payload); + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + onClose.complete(null); + } + } + static class FrameSizeLimitClientHandler implements WebSocketCallbacksHandler, WebSocketFrameListener { final int payloadSize; @@ -230,7 +618,7 @@ protected void initChannel(SocketChannel ch) { .channel(); } - static class FrameSizeLimitServerHandler + static class ValidationTestServerHandler implements WebSocketFrameListener, WebSocketCallbacksHandler { final CompletableFuture<Void> onClose = new CompletableFuture<>(); @@ -246,6 +634,8 @@ public WebSocketFrameListener exchange( @Override public void onChannelRead( ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload) { + //noinspection NonAtomicOperationOnVolatileField written from single thread + framesReceived++; payload.release(); } diff --git a/netty-websocket-http1/gradle.lockfile b/netty-websocket-http1/gradle.lockfile index b9d328d..0cf3816 100644 --- a/netty-websocket-http1/gradle.lockfile +++ b/netty-websocket-http1/gradle.lockfile @@ -7,13 +7,13 @@ com.google.errorprone:javac-shaded:9+181-r4173-1=googleJavaFormat1.6 com.google.googlejavaformat:google-java-format:1.6=googleJavaFormat1.6 com.google.guava:guava:22.0=googleJavaFormat1.6 com.google.j2objc:j2objc-annotations:1.1=googleJavaFormat1.6 -io.netty:netty-buffer:4.1.86.Final=compileClasspath -io.netty:netty-codec-http:4.1.86.Final=compileClasspath -io.netty:netty-codec:4.1.86.Final=compileClasspath -io.netty:netty-common:4.1.86.Final=compileClasspath -io.netty:netty-handler:4.1.86.Final=compileClasspath -io.netty:netty-resolver:4.1.86.Final=compileClasspath -io.netty:netty-transport-native-unix-common:4.1.86.Final=compileClasspath -io.netty:netty-transport:4.1.86.Final=compileClasspath +io.netty:netty-buffer:4.1.87.Final=compileClasspath +io.netty:netty-codec-http:4.1.87.Final=compileClasspath +io.netty:netty-codec:4.1.87.Final=compileClasspath +io.netty:netty-common:4.1.87.Final=compileClasspath +io.netty:netty-handler:4.1.87.Final=compileClasspath +io.netty:netty-resolver:4.1.87.Final=compileClasspath +io.netty:netty-transport-native-unix-common:4.1.87.Final=compileClasspath +io.netty:netty-transport:4.1.87.Final=compileClasspath org.codehaus.mojo:animal-sniffer-annotations:1.14=googleJavaFormat1.6 empty=annotationProcessor diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/DefaultWebSocketDecoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/DefaultWebSocketDecoder.java index 29c5ba0..73212b1 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/DefaultWebSocketDecoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/DefaultWebSocketDecoder.java @@ -22,6 +22,9 @@ import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus; final class DefaultWebSocketDecoder extends WebSocketDecoder { + WebSocketFrameFactory frameFactory; + + final int maxFramePayloadLength; int state = STATE_NON_PARTIAL; ByteBuf partialPrefix; int partialMask; @@ -38,7 +41,7 @@ final class DefaultWebSocketDecoder extends WebSocketDecoder { int fragmentedTotalLength = WebSocketProtocol.VALIDATION_RESULT_NON_FRAGMENTING; DefaultWebSocketDecoder(int maxFramePayloadLength) { - super(maxFramePayloadLength); + this.maxFramePayloadLength = maxFramePayloadLength; } @Override @@ -57,6 +60,20 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); } + @Override + public void frameListener( + ChannelHandlerContext ctx, + WebSocketFrameListener webSocketFrameListener, + WebSocketFrameFactory frameFactory) { + super.frameListener(ctx, webSocketFrameListener, frameFactory); + this.frameFactory = frameFactory; + } + + @Override + WebSocketFrameFactory frameFactory() { + return frameFactory; + } + @Override void decode(ChannelHandlerContext ctx, ByteBuf in) { int st = state; diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/MaskingWebSocketEncoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/MaskingWebSocketEncoder.java index 4dd328c..ed90dbf 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/MaskingWebSocketEncoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/MaskingWebSocketEncoder.java @@ -48,7 +48,7 @@ public WebSocketFrameFactory frameFactory(ChannelHandlerContext ctx) { return FrameFactory.INSTANCE; } - static class FrameFactory implements WebSocketFrameFactory { + static class FrameFactory implements WebSocketFrameFactory, WebSocketFrameFactory.Encoder { static final int PREFIX_SIZE_SMALL = 6; static final int BINARY_FRAME_SMALL = OPCODE_BINARY << 8 | /*FIN*/ (byte) 1 << 15 | /*MASK*/ (byte) 1 << 7; @@ -136,9 +136,51 @@ public ByteBuf createPongFrame(ByteBufAllocator allocator, int payloadSize) { public ByteBuf mask(ByteBuf frame) { int maskIndex = frame.readerIndex(); int mask = frame.getInt(maskIndex); - int cur = maskIndex + /*mask size*/ 4; - int end = frame.writerIndex(); + mask(mask, frame, maskIndex + /*mask size*/ 4, frame.writerIndex()); + return frame.readerIndex(0); + } + + @Override + public Encoder encoder() { + return this; + } + + @Override + public ByteBuf encodeBinaryFrame(ByteBuf binaryFrame) { + int frameSize = binaryFrame.readableBytes(); + int smallPrefixSize = 6; + if (frameSize <= 125 + smallPrefixSize) { + int payloadSize = frameSize - smallPrefixSize; + binaryFrame.setShort(0, BINARY_FRAME_SMALL | payloadSize); + int mask = mask(); + binaryFrame.setInt(2, mask); + return mask(mask, binaryFrame, smallPrefixSize, binaryFrame.writerIndex()); + } + + int mediumPrefixSize = 8; + if (frameSize <= 65_535 + mediumPrefixSize) { + int payloadSize = frameSize - mediumPrefixSize; + int mask = mask(); + binaryFrame.setLong(0, ((BINARY_FRAME_MEDIUM | (long) payloadSize) << 32) | mask); + return mask(mask, binaryFrame, mediumPrefixSize, binaryFrame.writerIndex()); + } + int payloadSize = frameSize - 12; + throw new IllegalArgumentException(payloadSizeLimit(payloadSize, 65_535)); + } + @Override + public int sizeofBinaryFrame(int payloadSize) { + if (payloadSize <= 125) { + return payloadSize + 6; + } + if (payloadSize < 65_535) { + return payloadSize + 8; + } + throw new IllegalArgumentException(payloadSizeLimit(payloadSize, 65_535)); + } + + static ByteBuf mask(int mask, ByteBuf frame, int start, int end) { + int cur = start; if (end - cur >= 8) { long longMask = (long) mask & 0xFFFFFFFFL; longMask |= longMask << 32; @@ -155,12 +197,7 @@ public ByteBuf mask(ByteBuf frame) { byte bytePayload = frame.getByte(cur); frame.setByte(cur, bytePayload ^ byteAtIndex(mask, maskOffset++ & 3)); } - return frame.readerIndex(0); - } - - @Override - public Encoder encoder() { - throw new UnsupportedOperationException("not implemented"); + return frame; } static int byteAtIndex(int mask, int index) { diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/NonMaskingWebSocketEncoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/NonMaskingWebSocketEncoder.java index 956b059..13d04d2 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/NonMaskingWebSocketEncoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/NonMaskingWebSocketEncoder.java @@ -32,7 +32,7 @@ final class NonMaskingWebSocketEncoder extends ChannelOutboundHandlerAdapter implements WebSocketCallbacksFrameEncoder { - static NonMaskingWebSocketEncoder INSTANCE = new NonMaskingWebSocketEncoder(); + static final NonMaskingWebSocketEncoder INSTANCE = new NonMaskingWebSocketEncoder(); private NonMaskingWebSocketEncoder() {} @@ -129,13 +129,15 @@ public Encoder encoder() { @Override public ByteBuf encodeBinaryFrame(ByteBuf binaryFrame) { int frameSize = binaryFrame.readableBytes(); - if (frameSize <= 127) { - int payloadSize = frameSize - 2; + int smallPrefixSize = 2; + if (frameSize <= 125 + smallPrefixSize) { + int payloadSize = frameSize - smallPrefixSize; return binaryFrame.setShort(0, BINARY_FRAME_SMALL | payloadSize); } - if (frameSize <= 65_539) { - int payloadSize = frameSize - 4; + int mediumPrefixSize = 4; + if (frameSize <= 65_535 + mediumPrefixSize) { + int payloadSize = frameSize - mediumPrefixSize; return binaryFrame.setInt(0, BINARY_FRAME_MEDIUM | payloadSize); } int payloadSize = frameSize - 8; @@ -150,7 +152,7 @@ public int sizeofBinaryFrame(int payloadSize) { if (payloadSize < 65_535) { return payloadSize + 4; } - throw new IllegalArgumentException(payloadSizeLimit(payloadSize, 125)); + throw new IllegalArgumentException(payloadSizeLimit(payloadSize, 65_535)); } static String payloadSizeLimit(int payloadSize, int limit) { diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/SmallWebSocketDecoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/SmallWebSocketDecoder.java index 4893d13..f86888b 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/SmallWebSocketDecoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/SmallWebSocketDecoder.java @@ -20,20 +20,13 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus; -final class SmallWebSocketDecoder extends WebSocketDecoder { - int state = STATE_NON_PARTIAL; - int partialPrefix; +abstract class SmallWebSocketDecoder extends WebSocketDecoder { + static final int MAX_FRAME_PAYLOAD_LENGTH = 125; + /* [8bit frag total length][8bit partial prefix][1bit unused][1bit fin][4bit opcode][2bit state] */ + int encodedState = encodeFragmentedLength(0, WebSocketProtocol.VALIDATION_RESULT_NON_FRAGMENTING); ByteBuf partialPayload; - int partialRemaining; - int opcode; - boolean fin; - /* non-negative value means fragmentation is in progress*/ - int fragmentedTotalLength = WebSocketProtocol.VALIDATION_RESULT_NON_FRAGMENTING; - - SmallWebSocketDecoder(int maxFramePayloadLength) { - super(maxFramePayloadLength); - } + SmallWebSocketDecoder() {} @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { @@ -46,14 +39,15 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { @Override void decode(ChannelHandlerContext ctx, ByteBuf in) { - int st = state; + int encodedSt = encodedState; + int st = decodeState(encodedSt); int readableBytes = in.readableBytes(); while (readableBytes > 0) { switch (st) { case STATE_NON_PARTIAL: if (readableBytes == 1) { st = STATE_PARTIAL_PREFIX; - partialPrefix = (in.readByte() << 8) & 0xFFFF; + encodedSt = encodePartialPrefix(encodedSt, in.readByte()); readableBytes = 0; } else { short prefix = in.readShort(); @@ -73,25 +67,28 @@ void decode(ChannelHandlerContext ctx, ByteBuf in) { "frames masking is not supported"); return; } - boolean finFlag = prefix < 0; int result = WebSocketProtocol.validate( - ctx, this, flags, code, length, fragmentedTotalLength, maxFramePayloadLength); + ctx, + this, + flags, + code, + length, + decodeFragmentedLength(encodedSt), + MAX_FRAME_PAYLOAD_LENGTH); if (result == WebSocketProtocol.VALIDATION_RESULT_INVALID) { return; } - fragmentedTotalLength = result; + encodedSt = encodeFragmentedLength(encodedSt, result); if (readableBytes >= length) { ByteBuf payload = in.readRetainedSlice(length); readableBytes -= length; onFrameRead(ctx, finFlag, code, payload); } else { - opcode = code; - fin = finFlag; + encodedSt = encodeFlags(encodedSt, code, finFlag); partialPayload = partialPayload(ctx, in, length); - partialRemaining = length - readableBytes; readableBytes = 0; st = STATE_PARTIAL_PAYLOAD; } @@ -99,8 +96,7 @@ void decode(ChannelHandlerContext ctx, ByteBuf in) { break; case STATE_PARTIAL_PREFIX: - int prefix = partialPrefix; - prefix |= in.readByte(); + int prefix = decodePartialPrefix(encodedSt) | in.readByte(); readableBytes -= 1; int flagsAndOpcode = prefix >> 8; @@ -115,14 +111,16 @@ void decode(ChannelHandlerContext ctx, ByteBuf in) { ctx, this, WebSocketCloseStatus.NORMAL_CLOSURE, "frames masking is not supported"); return; } + int fragmentedTotalLength = decodeFragmentedLength(encodedSt); int result = WebSocketProtocol.validate( - ctx, this, flags, code, length, fragmentedTotalLength, maxFramePayloadLength); + ctx, this, flags, code, length, fragmentedTotalLength, MAX_FRAME_PAYLOAD_LENGTH); if (result == WebSocketProtocol.VALIDATION_RESULT_INVALID) { return; } fragmentedTotalLength = result; + encodedSt = encodeFragmentedLength(encodedSt, fragmentedTotalLength); if (readableBytes >= length) { ByteBuf payload = in.readRetainedSlice(length); @@ -130,28 +128,27 @@ void decode(ChannelHandlerContext ctx, ByteBuf in) { st = STATE_NON_PARTIAL; onFrameRead(ctx, finFlag, code, payload); } else { - opcode = code; - fin = finFlag; + encodedSt = encodeFlags(encodedSt, code, finFlag); partialPayload = partialPayload(ctx, in, length); - partialRemaining = length - readableBytes; readableBytes = 0; st = STATE_PARTIAL_PAYLOAD; } break; case STATE_PARTIAL_PAYLOAD: - int remaining = partialRemaining; - int toRead = Math.min(readableBytes, remaining); ByteBuf partial = partialPayload; + int remaining = partial.capacity() - partial.writerIndex(); + int toRead = Math.min(readableBytes, remaining); partial.writeBytes(in, toRead); remaining -= toRead; readableBytes -= toRead; if (remaining == 0) { partialPayload = null; + int opcodeFin = decodeFlags(encodedSt); + int opcode = decodeFlagOpcode(opcodeFin); + boolean fin = decodeFlagFin(opcodeFin); onFrameRead(ctx, fin, opcode, partial); st = STATE_NON_PARTIAL; - } else { - partialRemaining = remaining; } break; @@ -160,15 +157,15 @@ void decode(ChannelHandlerContext ctx, ByteBuf in) { break; default: - throw new IllegalStateException("unexpected decoding state: " + state); + throw new IllegalStateException("unexpected decoding state: " + st); } } - state = st; + encodedState = encodeState(encodedSt, st); } @Override void closeInbound() { - state = STATE_CLOSED_INBOUND; + encodedState = encodeState(encodedState, STATE_CLOSED_INBOUND); } static ByteBuf partialPayload(ChannelHandlerContext ctx, ByteBuf in, int length) { @@ -176,4 +173,64 @@ static ByteBuf partialPayload(ChannelHandlerContext ctx, ByteBuf in, int length) partial.writeBytes(in); return partial; } + + /* layout description is on "encodedState" field */ + + static int encodeState(int encodedState, int state) { + return encodedState & 0xFF_FF_FF_FC | state; + } + + static int decodeState(int encodedState) { + return encodedState & 0x3; + } + + static int encodeFlags(int encodedState, int opcode, boolean fin) { + int flags = (fin ? (byte) 1 : 0) << 6 | opcode << 2; + return encodedState & 0xFF_FF_FF_F3 | flags; + } + + static int decodeFlags(int encodedState) { + return (encodedState & 0xFC) >> 2; + } + + static int decodeFlagOpcode(int flags) { + return flags & 0xF; + } + + static boolean decodeFlagFin(int flags) { + return (flags & 0x10) == 0x10; + } + + static int encodePartialPrefix(int encodedState, byte partialPrefix) { + return encodedState & 0xFF_FF_00_FF | (partialPrefix << 8) & 0xFFFF; + } + + static int decodePartialPrefix(int encodedState) { + return encodedState & 0xFF_00; + } + + static int encodeFragmentedLength(int encodedState, int fragmentedTotalLength) { + return encodedState & 0xFF_FF | fragmentedTotalLength << 16; + } + + /* non-negative value means fragmentation is in progress*/ + static int decodeFragmentedLength(int encodedState) { + return (byte) ((encodedState & 0xFF_00_00) >> 16); + } + + static final class WithMaskingEncoder extends SmallWebSocketDecoder { + + @Override + WebSocketFrameFactory frameFactory() { + return MaskingWebSocketEncoder.FrameFactory.INSTANCE; + } + } + + static final class WithNonMaskingEncoder extends SmallWebSocketDecoder { + + @Override + WebSocketFrameFactory frameFactory() { + return NonMaskingWebSocketEncoder.FrameFactory.INSTANCE; + } + } } diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCallbacksFrameDecoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCallbacksFrameDecoder.java index de6037e..66711d4 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCallbacksFrameDecoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCallbacksFrameDecoder.java @@ -26,6 +26,26 @@ void frameListener( WebSocketFrameListener webSocketFrameListener, WebSocketFrameFactory frameFactory); + static WebSocketCallbacksFrameDecoder frameDecoder( + boolean maskPayload, + int maxFramePayloadLength, + boolean expectMaskedFrames, + boolean allowMaskMismatch) { + + /*strict*/ + if (!allowMaskMismatch) { + /*small unmasked*/ + if (maxFramePayloadLength <= 125 && !expectMaskedFrames) { + return maskPayload + ? new SmallWebSocketDecoder.WithMaskingEncoder() + : new SmallWebSocketDecoder.WithNonMaskingEncoder(); + } + throw new IllegalArgumentException( + "enforcing strictly masked/unmasked frames is not supported"); + } + return new DefaultWebSocketDecoder(maxFramePayloadLength); + } + static WebSocketCallbacksFrameDecoder frameDecoder( int maxFramePayloadLength, boolean expectMaskedFrames, boolean allowMaskMismatch) { @@ -33,7 +53,12 @@ static WebSocketCallbacksFrameDecoder frameDecoder( if (!allowMaskMismatch) { /*small unmasked*/ if (maxFramePayloadLength <= 125 && !expectMaskedFrames) { - return new SmallWebSocketDecoder(maxFramePayloadLength); + throw new IllegalArgumentException( + "small decoder: use frameDecoder(" + + "boolean maskPayload," + + "int maxFramePayloadLength," + + "boolean expectMaskedFrames," + + "boolean allowMaskMismatch) instead of deprecated one"); } throw new IllegalArgumentException( "enforcing strictly masked/unmasked frames is not supported"); diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index aff21a7..70d8bc9 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -55,7 +55,7 @@ public WebSocketClientHandshaker( @Override protected WebSocketFrameDecoder newWebsocketDecoder() { return WebSocketCallbacksFrameDecoder.frameDecoder( - maxFramePayloadLength(), expectMaskedFrames, allowMaskMismatch); + performMasking, maxFramePayloadLength(), expectMaskedFrames, allowMaskMismatch); } @Override diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java index 8f20257..ecdb399 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketDecoder.java @@ -28,16 +28,14 @@ abstract class WebSocketDecoder implements ChannelInboundHandler, WebSocketCallb static final int STATE_PARTIAL_PAYLOAD = 3; static final int STATE_CLOSED_INBOUND = 4; - final int maxFramePayloadLength; WebSocketFrameListener webSocketFrameListener; - WebSocketFrameFactory frameFactory; - WebSocketDecoder(int maxFramePayloadLength) { - this.maxFramePayloadLength = maxFramePayloadLength; - } + WebSocketDecoder() {} abstract void decode(ChannelHandlerContext ctx, ByteBuf buf); + abstract WebSocketFrameFactory frameFactory(); + @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { WebSocketFrameListener listener = webSocketFrameListener; @@ -67,7 +65,6 @@ public void frameListener( WebSocketFrameListener webSocketFrameListener, WebSocketFrameFactory frameFactory) { this.webSocketFrameListener = webSocketFrameListener; - this.frameFactory = frameFactory; } @Override diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketProtocol.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketProtocol.java index ed5b0aa..5aac516 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketProtocol.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketProtocol.java @@ -34,7 +34,7 @@ public final class WebSocketProtocol { public static final byte OPCODE_PONG = 0xA; /*validation*/ - static final int VALIDATION_RESULT_INVALID = Integer.MIN_VALUE; + static final int VALIDATION_RESULT_INVALID = Byte.MIN_VALUE; static final int VALIDATION_RESULT_NON_FRAGMENTING = -1; static int validate( @@ -203,7 +203,7 @@ static void close( WebSocketDecoder webSocketDecoder, WebSocketCloseStatus status, String msg) { - WebSocketFrameFactory frameFactory = webSocketDecoder.frameFactory; + WebSocketFrameFactory frameFactory = webSocketDecoder.frameFactory(); WebSocketFrameListener frameListener = webSocketDecoder.webSocketFrameListener; ByteBuf closeFrame = @@ -220,8 +220,8 @@ public static void validateDecoderConfig( boolean expectMaskedFrames, boolean allowMaskMismatch) { - if (maxFramePayloadLength < 0 || maxFramePayloadLength > 65_535) { - throw new IllegalArgumentException("maxFramePayloadLength must be in range [0; 65535]"); + if (maxFramePayloadLength < 125 || maxFramePayloadLength > 65_535) { + throw new IllegalArgumentException("maxFramePayloadLength must be in range [125; 65535]"); } if (allowExtensions) { throw new IllegalArgumentException("extensions are not supported"); @@ -255,12 +255,23 @@ private WebSocketProtocol() {} /*for use with external websocket handlers, e.g. websocket-http2*/ + /** @deprecated use {@link #frameDecoder(boolean, int, boolean, boolean)} instead */ + @Deprecated public static WebSocketFrameDecoder frameDecoder( int maxFramePayloadLength, boolean expectMaskedFrames, boolean allowMaskMismatch) { return WebSocketCallbacksFrameDecoder.frameDecoder( maxFramePayloadLength, expectMaskedFrames, allowMaskMismatch); } + public static WebSocketFrameDecoder frameDecoder( + boolean maskPayload, + int maxFramePayloadLength, + boolean expectMaskedFrames, + boolean allowMaskMismatch) { + return WebSocketCallbacksFrameDecoder.frameDecoder( + maskPayload, maxFramePayloadLength, expectMaskedFrames, allowMaskMismatch); + } + public static WebSocketFrameEncoder frameEncoder(boolean expectMaskedFrames) { return WebSocketCallbacksFrameEncoder.frameEncoder(expectMaskedFrames); } diff --git a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index df16eb8..aa4ef2b 100644 --- a/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -41,6 +41,7 @@ protected WebSocketFrameDecoder newWebsocketDecoder() { WebSocketDecoderConfig decoderConfig = decoderConfig(); return WebSocketCallbacksFrameDecoder.frameDecoder( + false, decoderConfig.maxFramePayloadLength(), decoderConfig.expectMaskedFrames(), decoderConfig.allowMaskMismatch()); diff --git a/netty-websocket-http1/src/main/resources/META-INF/native-image/com.jauntsdn.netty/netty-websocket-http1/generated/handlers/reflect-config.json b/netty-websocket-http1/src/main/resources/META-INF/native-image/com.jauntsdn.netty/netty-websocket-http1/generated/handlers/reflect-config.json new file mode 100644 index 0000000..543aa30 --- /dev/null +++ b/netty-websocket-http1/src/main/resources/META-INF/native-image/com.jauntsdn.netty/netty-websocket-http1/generated/handlers/reflect-config.json @@ -0,0 +1,79 @@ +[ + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.DefaultWebSocketDecoder", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.DefaultWebSocketDecoder" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketDecoder", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketDecoder" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.MaskingWebSocketEncoder", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.MaskingWebSocketEncoder" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.SmallWebSocketDecoder", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.SmallWebSocketDecoder" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.SmallWebSocketDecoder$WithMaskingEncoder", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.SmallWebSocketDecoder$WithMaskingEncoder" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.SmallWebSocketDecoder$WithNonMaskingEncoder", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.SmallWebSocketDecoder$WithNonMaskingEncoder" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketClientHandshaker", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketClientHandshaker" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketProtocol", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketProtocol" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketServerHandshaker", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketServerHandshaker" + }, + "queryAllPublicMethods": true + }, + { + "name": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler", + "condition": { + "typeReachable": "com.jauntsdn.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler" + }, + "queryAllPublicMethods": true + } +] \ No newline at end of file