From 3d532a821fbfee0c289ed072b7ff77e629114940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Sat, 13 Jan 2024 11:34:04 +0100 Subject: [PATCH] add epoll native --- NOTICE.txt | 1 + gradle/test/junit5.gradle | 9 +- netty-channel-epoll-native/build.gradle | 82 ++ .../libnetty_transport_native_epoll_x86_64.so | Bin 0 -> 103040 bytes .../epoll/DetectPeerCloseWithoutReadTest.java | 212 +++++ .../EpollAbstractDomainSocketEchoTest.java | 28 + .../channel/epoll/EpollChannelConfigTest.java | 93 +++ ...pollCompositeBufferGatheringWriteTest.java | 39 + .../epoll/EpollDatagramChannelConfigTest.java | 33 + .../epoll/EpollDatagramChannelTest.java | 115 +++ .../EpollDatagramConnectNotExistsTest.java | 30 + .../epoll/EpollDatagramMulticastIPv6Test.java | 30 + ...DatagramMulticastIpv6WithIpv4AddrTest.java | 32 + .../epoll/EpollDatagramMulticastTest.java | 29 + .../EpollDatagramScatteringReadTest.java | 307 ++++++++ .../EpollDatagramUnicastIPv6MappedTest.java | 29 + .../epoll/EpollDatagramUnicastIPv6Test.java | 29 + .../epoll/EpollDatagramUnicastTest.java | 190 +++++ .../EpollDetectPeerCloseWithoutReadTest.java | 37 + .../epoll/EpollDomainDatagramChannelTest.java | 36 + .../epoll/EpollDomainDatagramPathTest.java | 76 ++ .../epoll/EpollDomainDatagramUnicastTest.java | 168 ++++ .../epoll/EpollDomainSocketAddressesTest.java | 45 ++ ...lDomainSocketDataReadInitialStateTest.java | 36 + .../epoll/EpollDomainSocketEchoTest.java | 35 + .../epoll/EpollDomainSocketFdTest.java | 116 +++ .../EpollDomainSocketFileRegionTest.java | 35 + .../EpollDomainSocketFixedLengthEchoTest.java | 37 + .../EpollDomainSocketGatheringWriteTest.java | 37 + .../EpollDomainSocketObjectEchoTest.java | 36 + .../epoll/EpollDomainSocketReuseFdTest.java | 36 + ...lDomainSocketShutdownOutputByPeerTest.java | 69 ++ ...lDomainSocketSslClientRenegotiateTest.java | 36 + .../epoll/EpollDomainSocketSslEchoTest.java | 36 + .../EpollDomainSocketSslGreetingTest.java | 36 + .../epoll/EpollDomainSocketStartTlsTest.java | 36 + .../EpollDomainSocketStringEchoTest.java | 36 + .../epoll/EpollETSocketAutoReadTest.java | 39 + ...ollETSocketConditionalWritabilityTest.java | 39 + ...EpollETSocketDataReadInitialStateTest.java | 39 + .../EpollETSocketExceptionHandlingTest.java | 39 + .../epoll/EpollETSocketHalfClosedTest.java | 39 + .../epoll/EpollETSocketReadPendingTest.java | 39 + .../EpollETSocketStringEchoBusyWaitTest.java | 31 + .../channel/epoll/EpollEventLoopTest.java | 189 +++++ .../EpollJdkLoopbackSocketSslEchoTest.java | 25 + .../epoll/EpollKQueueIovArrayTest.java | 26 + .../epoll/EpollLTSocketAutoReadTest.java | 39 + ...ollLTSocketConditionalWritabilityTest.java | 39 + ...EpollLTSocketDataReadInitialStateTest.java | 39 + .../EpollLTSocketExceptionHandlingTest.java | 39 + .../epoll/EpollLTSocketHalfClosedTest.java | 39 + .../epoll/EpollLTSocketReadPendingTest.java | 39 + .../EpollLTSocketStringEchoBusyWaitTest.java | 31 + .../channel/epoll/EpollReuseAddrTest.java | 263 +++++++ .../EpollServerSocketChannelConfigTest.java | 103 +++ .../epoll/EpollSocketAddressesTest.java | 42 + .../epoll/EpollSocketChannelConfigTest.java | 178 +++++ ...EpollSocketChannelNotYetConnectedTest.java | 29 + .../channel/epoll/EpollSocketChannelTest.java | 123 +++ .../epoll/EpollSocketCloseForciblyTest.java | 30 + .../channel/epoll/EpollSocketConnectTest.java | 31 + .../EpollSocketConnectionAttemptTest.java | 29 + .../channel/epoll/EpollSocketEchoTest.java | 31 + .../epoll/EpollSocketFileRegionTest.java | 31 + .../epoll/EpollSocketFixedLengthEchoTest.java | 31 + .../epoll/EpollSocketGatheringWriteTest.java | 31 + .../epoll/EpollSocketMultipleConnectTest.java | 43 + .../epoll/EpollSocketObjectEchoTest.java | 31 + .../channel/epoll/EpollSocketRstTest.java | 49 ++ .../EpollSocketShutdownOutputByPeerTest.java | 29 + .../EpollSocketShutdownOutputBySelfTest.java | 29 + .../EpollSocketSslClientRenegotiateTest.java | 30 + .../channel/epoll/EpollSocketSslEchoTest.java | 30 + .../epoll/EpollSocketSslGreetingTest.java | 30 + .../epoll/EpollSocketSslSessionReuseTest.java | 30 + .../epoll/EpollSocketStartTlsTest.java | 30 + .../EpollSocketStringEchoBusyWaitTest.java | 100 +++ .../epoll/EpollSocketStringEchoTest.java | 31 + .../channel/epoll/EpollSocketTcpMd5Test.java | 123 +++ .../netty/channel/epoll/EpollSocketTest.java | 81 ++ .../epoll/EpollSocketTestPermutation.java | 264 +++++++ .../netty/channel/epoll/EpollSpliceTest.java | 313 ++++++++ .../io/netty/channel/epoll/EpollTest.java | 75 ++ .../epoll/EpollWriteBeforeRegisteredTest.java | 30 + .../io/netty/channel/epoll/IovArrayTest.java | 64 ++ .../netty/channel/epoll/LinuxSocketTest.java | 97 +++ .../io/netty/channel/epoll/NativeTest.java | 51 ++ .../io/netty/channel/epoll/SocketTest.java | 134 ++++ .../io/netty/channel/epoll/UnixTestUtils.java | 81 ++ .../src/test/resources/logging.properties | 7 + netty-channel-sctp/build.gradle | 3 + .../sctp/DefaultSctpChannelConfig.java | 245 ++++++ .../sctp/DefaultSctpServerChannelConfig.java | 227 ++++++ .../io/netty/channel/sctp/SctpChannel.java | 114 +++ .../netty/channel/sctp/SctpChannelConfig.java | 135 ++++ .../netty/channel/sctp/SctpChannelOption.java | 48 ++ .../io/netty/channel/sctp/SctpMessage.java | 204 +++++ .../channel/sctp/SctpNotificationHandler.java | 70 ++ .../netty/channel/sctp/SctpServerChannel.java | 87 ++ .../channel/sctp/SctpServerChannelConfig.java | 130 +++ .../channel/sctp/nio/NioSctpChannel.java | 401 ++++++++++ .../sctp/nio/NioSctpServerChannel.java | 239 ++++++ .../netty/channel/sctp/nio/package-info.java | 21 + .../channel/sctp/oio/OioSctpChannel.java | 474 +++++++++++ .../sctp/oio/OioSctpServerChannel.java | 308 ++++++++ .../netty/channel/sctp/oio/package-info.java | 23 + .../io/netty/channel/sctp/package-info.java | 20 + .../src/main/java/module-info.java | 9 + netty-handler-codec-sctp/build.gradle | 4 + .../sctp/SctpInboundByteStreamHandler.java | 64 ++ .../sctp/SctpMessageCompletionHandler.java | 80 ++ .../sctp/SctpMessageToMessageDecoder.java | 39 + .../sctp/SctpOutboundByteStreamHandler.java | 58 ++ .../handler/codec/sctp/package-info.java | 20 + .../src/main/java/module-info.java | 8 + netty-testsuite/build.gradle | 18 + .../transport/AbstractComboTestsuiteTest.java | 62 ++ .../AbstractSingleThreadEventLoopTest.java | 316 ++++++++ .../transport/AbstractTestsuiteTest.java | 59 ++ .../transport/DefaultEventLoopTest.java | 68 ++ .../testsuite/transport/NioEventLoopTest.java | 46 ++ .../transport/TestsuitePermutation.java | 45 ++ .../testsuite/transport/package-info.java | 20 + .../transport/sctp/AbstractSctpTest.java | 42 + .../transport/sctp/SctpEchoTest.java | 188 +++++ .../transport/sctp/SctpTestPermutation.java | 130 +++ .../transport/sctp/package-info.java | 20 + .../socket/AbstractClientSocketTest.java | 43 + .../socket/AbstractDatagramTest.java | 64 ++ .../socket/AbstractServerSocketTest.java | 47 ++ .../socket/AbstractSocketReuseFdTest.java | 189 +++++ ...bstractSocketShutdownOutputByPeerTest.java | 180 +++++ .../transport/socket/AbstractSocketTest.java | 47 ++ .../CompositeBufferGatheringWriteTest.java | 309 ++++++++ .../socket/DatagramConnectNotExistsTest.java | 84 ++ .../socket/DatagramMulticastIPv6Test.java | 38 + .../socket/DatagramMulticastTest.java | 232 ++++++ .../socket/DatagramUnicastIPv6MappedTest.java | 51 ++ .../socket/DatagramUnicastIPv6Test.java | 50 ++ .../socket/DatagramUnicastInetTest.java | 170 ++++ .../transport/socket/DatagramUnicastTest.java | 495 ++++++++++++ .../socket/ServerSocketSuspendTest.java | 116 +++ .../transport/socket/SocketAddressesTest.java | 99 +++ .../transport/socket/SocketAutoReadTest.java | 232 ++++++ .../socket/SocketBufReleaseTest.java | 121 +++ .../socket/SocketCancelWriteTest.java | 127 +++ .../SocketChannelNotYetConnectedTest.java | 122 +++ .../socket/SocketCloseForciblyTest.java | 56 ++ .../SocketConditionalWritabilityTest.java | 133 ++++ .../transport/socket/SocketConnectTest.java | 239 ++++++ .../socket/SocketConnectionAttemptTest.java | 178 +++++ .../SocketDataReadInitialStateTest.java | 199 +++++ .../transport/socket/SocketEchoTest.java | 310 ++++++++ .../socket/SocketExceptionHandlingTest.java | 119 +++ .../socket/SocketFileRegionTest.java | 384 +++++++++ .../socket/SocketFixedLengthEchoTest.java | 199 +++++ .../socket/SocketGatheringWriteTest.java | 278 +++++++ .../socket/SocketHalfClosedTest.java | 740 ++++++++++++++++++ .../socket/SocketMultipleConnectTest.java | 84 ++ .../socket/SocketObjectEchoTest.java | 204 +++++ .../socket/SocketReadPendingTest.java | 212 +++++ .../transport/socket/SocketRstTest.java | 161 ++++ .../SocketShutdownOutputByPeerTest.java | 51 ++ .../SocketShutdownOutputBySelfTest.java | 321 ++++++++ .../transport/socket/SocketSpdyEchoTest.java | 323 ++++++++ .../SocketSslClientRenegotiateTest.java | 267 +++++++ .../transport/socket/SocketSslEchoTest.java | 596 ++++++++++++++ .../socket/SocketSslGreetingTest.java | 273 +++++++ .../socket/SocketSslSessionReuseTest.java | 216 +++++ .../transport/socket/SocketStartTlsTest.java | 338 ++++++++ .../socket/SocketStringEchoTest.java | 206 +++++ .../socket/SocketTestPermutation.java | 223 ++++++ .../socket/TrafficShapingHandlerTest.java | 604 ++++++++++++++ .../socket/WriteBeforeRegisteredTest.java | 61 ++ .../transport/socket/package-info.java | 20 + .../io/netty/testsuite/util/TestUtils.java | 273 +++++++ .../io/netty/testsuite/util/package-info.java | 20 + netty-util/src/main/java/module-info.java | 4 + settings.gradle | 8 +- 180 files changed, 19923 insertions(+), 9 deletions(-) create mode 100644 netty-channel-epoll-native/build.gradle create mode 100755 netty-channel-epoll-native/src/main/resources/META-INF/native/libnetty_transport_native_epoll_x86_64.so create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/IovArrayTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/NativeTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/SocketTest.java create mode 100644 netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/UnixTestUtils.java create mode 100644 netty-channel-epoll-native/src/test/resources/logging.properties create mode 100644 netty-channel-sctp/build.gradle create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelOption.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpMessage.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelConfig.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpChannel.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpServerChannel.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/package-info.java create mode 100755 netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpChannel.java create mode 100755 netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpServerChannel.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/package-info.java create mode 100644 netty-channel-sctp/src/main/java/io/netty/channel/sctp/package-info.java create mode 100644 netty-channel-sctp/src/main/java/module-info.java create mode 100644 netty-handler-codec-sctp/build.gradle create mode 100644 netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpInboundByteStreamHandler.java create mode 100644 netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java create mode 100644 netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageToMessageDecoder.java create mode 100644 netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpOutboundByteStreamHandler.java create mode 100644 netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/package-info.java create mode 100644 netty-handler-codec-sctp/src/main/java/module-info.java create mode 100644 netty-testsuite/build.gradle create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractComboTestsuiteTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractSingleThreadEventLoopTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractTestsuiteTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/DefaultEventLoopTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/NioEventLoopTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/TestsuitePermutation.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/package-info.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/AbstractSctpTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpTestPermutation.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/package-info.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractClientSocketTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractServerSocketTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketReuseFdTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketShutdownOutputByPeerTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/CompositeBufferGatheringWriteTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramConnectNotExistsTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastIPv6Test.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6MappedTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6Test.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastInetTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAddressesTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAutoReadTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketBufReleaseTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCancelWriteTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketChannelNotYetConnectedTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCloseForciblyTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConditionalWritabilityTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectionAttemptTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketDataReadInitialStateTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketExceptionHandlingTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFixedLengthEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketGatheringWriteTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketHalfClosedTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketMultipleConnectTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketObjectEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketReadPendingTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketRstTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputByPeerTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslClientRenegotiateTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslSessionReuseTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStringEchoTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/TrafficShapingHandlerTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/WriteBeforeRegisteredTest.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/package-info.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/util/TestUtils.java create mode 100644 netty-testsuite/src/main/java/io/netty/testsuite/util/package-info.java diff --git a/NOTICE.txt b/NOTICE.txt index 8321e3f..2b8df6f 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -25,6 +25,7 @@ The following changes were performed on the original source code: - removed all macos related code (including kqueue) - removed all aarch64 related code - removed the direct brotli4j dependency by rewriting Brotli4jOptions to not use Encoder.Parameters +- removed deprecated channel-udt (transport-udt) Challenges for Netty build on JDK 21 diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle index 656e636..d47aa30 100644 --- a/gradle/test/junit5.gradle +++ b/gradle/test/junit5.gradle @@ -10,13 +10,11 @@ dependencies { test { useJUnitPlatform() - failFast = true + failFast = false testLogging { events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' showStandardStreams = true } - minHeapSize = "1g" // initial heap size - maxHeapSize = "2g" // maximum heap size jvmArgs '--add-exports=java.base/jdk.internal=ALL-UNNAMED', '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED', '--add-exports=java.base/sun.nio.ch=ALL-UNNAMED', @@ -30,11 +28,6 @@ test { '--add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED', '-Dio.netty.bootstrap.extensions=serviceload' systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties' - // we have remove native images, this is no longer used - systemProperty "nativeImage.handlerMetadataGroupId", "io.netty" - // we have remove native images - // we have remove native images, this is no longer used - systemProperty "nativeimage.handlerMetadataArtifactId", "netty-transport" afterSuite { desc, result -> if (!desc.parent) { println "\nTest result: ${result.resultType}" diff --git a/netty-channel-epoll-native/build.gradle b/netty-channel-epoll-native/build.gradle new file mode 100644 index 0000000..aac2e31 --- /dev/null +++ b/netty-channel-epoll-native/build.gradle @@ -0,0 +1,82 @@ + +apply plugin: 'com.google.osdetector' + +dependencies { + testImplementation project(':netty-channel-unix') + testImplementation project(':netty-channel-epoll') + testImplementation project(':netty-testsuite') + testImplementation project(':netty-handler') + testImplementation testLibs.assertj + testImplementation testLibs.rerunner.jupiter + testRuntimeOnly project(path: ':netty-tcnative-boringssl-static', configuration: osdetector.classifier) +} + +task nettyEpollLinuxX8664(type: Jar) { + archiveBaseName.set('netty-channel-epoll-native') + archiveClassifier.set('linux-x86_64') + version rootProject.version + from (sourceSets.main.output) { + include 'META-INF/native/libnetty_transport_native_epoll_x86_64.so' + } +} +assemble.dependsOn(nettyEpollLinuxX8664) + +configurations { + 'linux-x86_64' { + canBeConsumed = true + canBeResolved = false + extendsFrom runtimeOnly + } +} + +artifacts { + 'linux-x86_64'(nettyEpollLinuxX8664) +} + +publishing { + publications { + publishNettyEpollLinuxX8664(MavenPublication) { + groupId rootProject.group + artifactId project.name + version rootProject.version + artifact nettyEpollLinuxX8664 + pom { + artifactId = project.name + name = project.name + version = project.version + description = rootProject.ext.description + url = rootProject.ext.url + inceptionYear = rootProject.ext.inceptionYear + packaging = 'jar' + organization { + name = rootProject.ext.organizationName + url = rootProject.ext.organizationUrl + } + developers { + developer { + id = 'jprante' + name = 'Jörg Prante' + email = 'joergprante@gmail.com' + url = 'https://xbib.org/joerg' + } + } + scm { + url = rootProject.ext.scmUrl + connection = rootProject.ext.scmConnection + developerConnection = rootProject.ext.scmDeveloperConnection + } + issueManagement { + system = rootProject.ext.issueManagementSystem + url = rootProject.ext.issueManagementUrl + } + licenses { + license { + name = rootProject.ext.licenseName + url = rootProject.ext.licenseUrl + distribution = 'repo' + } + } + } + } + } +} \ No newline at end of file diff --git a/netty-channel-epoll-native/src/main/resources/META-INF/native/libnetty_transport_native_epoll_x86_64.so b/netty-channel-epoll-native/src/main/resources/META-INF/native/libnetty_transport_native_epoll_x86_64.so new file mode 100755 index 0000000000000000000000000000000000000000..61e85d2231ffb22ac84e33e4b95fe117b9d10b92 GIT binary patch literal 103040 zcmeFa4SZC^^*?+AL;^@RDAf2KEo$%u5)}<9nuT55#SKItic(BsvLVqVyC%C3tmx`y zZLaI;YPC&U?LYlh+o;vXRxqI8hPNatHh^LTEAf@P1~pcNh-jYgnVGwLcars`zkdGD z=kq+KmE8HxnKNh3oS8XuX70^xR>zzXDJhEnGg7%!iKi4LPh>=baWqfKRmLFac;z@o zJ4!D+%M2ld}dI!_a~RC+AnY5Dh<5Xbf3#&WE_UQtf^J?b)!>%WcVjzCuUY~GIW zC5gY?NAq$`6Zvnw+z2G8Twm}{=0l$p_1{~ z`PVD|K6UJzjytdZZQ;D@KL2&+ViJM!t@w}1k$j_kit>n3%&<8~UylFY@9MnTIWy$A z6=_$>l=3tYGn$}0kI zmT;x0Xj!$Zrl`(SQtK%yQi|-0@{7uxwa#UgbslH!qWsxat{Uf}lIyFSJa71%qSE@3 zqVmd`lB&v^L0IIht*vntRk=z_Je95*rOsJXR$d7f%bcD%SLuy4CDrWN?R3_%=Q@tN z+#aRWRa4_E^(fa@)|4rwB~@Uel-D|)O0BbWd3D_~g%~(JN}ao^vecn!m&a}rWYd1;NON^vfC)_BUxidNKCdQeiNKwqX*JF6jsQn#{>s#L0Zy_~t5 zxSy+dnWP*l1vgJ+wX+u7c!fGUrQGFq)+puFQl-vQ>v2_~_9|DMGgbf)z#8r!)sdq0!XY*uH zt>Uh9JI`hRDoiWOmAV>tZ6(x1Dk*iZM6;_ZYi?v+NZO}fURPe zn$l`F{U%B1R|)#aQ;YvAci=bCx}J$;)xi|TVPD7xVMbIzZdGc_lB z`qVjOwdX9HXD(Q1o4*Jwl*=9VS+k4IojN1-bl&vXW6t@p$K2S{c~j4gKgS7jvg7E~ zI2>no?$r46g&5f*;GgRMOMy?TKaa%TB>p@Sp6lp@^hhNwA)SK#&`4#h;5ROoXQU#E zG|VR>8C42?(=-2H3jAaGANk4SlrGF!x_^FbW#xF9g(kw0CT#V6zdk|%%}FBt085Wm zrigR~qaUN3FVeNgR*q3n@1-LB45J^d%oXV{OCO;WinNE(rz*uFJ&ExfrBsQu_c-D= zQdut2$FTen${k44kG`+c_d(r@INl`oTRI*Vc(a7->4=1%^e*SqDdE!u-YwzjHJrXj z!p{@*y%IiC;F^R-syUy23AYIP0SVXnC^M-yN%Q=tw~GHxlkh7ApL7W?6L^M%7Z-6p znG$}3pwE(UolmxeD}qn1gx@0gwJ6??iPF+CHyAACoJJQpC$><6MUK_yg~43 zk#L<)tAwWuK5Y_yzu*&*aJ|1eCA?MiSGR=!QSj-JaGg)Dg!=>^O~P9RpMD9~`3y*S zf#9QDmT3PG!6!|^bw245o+i9Pp5?Ie7YsPK=A32@I``8uY~J-GzrfX zeEKE4MDQ7qaGj5mmuUa}*Kj?jN%&2IPr8Kbd@>|FBKTxV_??1JmW1CY@N5b1y_(CD zE8$IoK2O3Q6}U~p3vT3m3M9N$&@YzobpkJza9;)IQz7A71-)CscM7~-!i$}pk59ti z7W9o0uFDga@Ng;T(;rjlfU&6B+IsJfy=L$ZGInn-gK4}u(O96ZQq)T|7;FBTY z`2x?B@VuKjpDYPqDCn~#yhz}=65cH2$&>IhL2r|Ay6Y+L!KX*UUlVw*gl7tQGzs4$==&vH@Am--FRtVAD6qjDkNOzof59|>6Y*U!KX*U%LJcZ3D@~(67Cj!`XzjY z;4>iMy8S4#6Yal1*mIhM-!1s0OSsM_L&AIS;G_|!fwXN838^0*~j5vTt35`K~3gzJ11OQJm&3qENQ9u$1iC0yr|A>nSpCsV>75`3~GT<4Q5;Xc79SHhnXeDWk* z=VO!bu;5c5;m-*^izQs=Q!L@lf=`8ncL_dj3D^15OL(i`wLlz9ua(+ zBz(W%(=6d%2)sqY(|^Usbwt8P(ZN1`Iwf59=es35(!}X|Bz)DsIo>PbV+DUr!rgT7 z0YCi`o+;=DB>Z%NE7nAN?*9YllP2M_1bw=M>wad2g!=@aObMSa_+&|Vk-)PhJYDd~ zmGE*wpC{qf0=G$c?{9g#3M4!z=od@4uAgEFFBW_%Bs?tmxMf`UY4sA`FZ$gl;SUKu zjS~Kdz{3)rF8DM{_`8C>MZ$GHtrFfZ?59n__X|D|3D?J0r-UobT+iJS{+Zy@BjGxq zUI|YZd^8FFTJY(YaGlS9gl7sq%A7f_|}t>-(Kz3C|PqR7m*Ef{$Cmb$RL~y!%-$ zk59rY1)oL<*XP5qglE6P`7}xR3c;sY!u4~a776z~#`&~L_#J{zn}n+Zk4SjIM$V^G z!oz~TTf+5o#vTdJ+{*d%O86fHA5Fq_KK&A2u!Zv(knkr2ALa5yJJJ=!lwy7VF~a5jMF#C_!^EkOZWwXPm6?G z1l}s)y_-0nHVI!K=pzz-y}&yqJX6TiE#WRf-y`972)tLqd!Od=XcFEm==&vnoxleq zJRDJpM+JSRgzJ7nmV`(C#`$DR_{V}zu7vA;RGx&p z+c_VbgdY-o3M5>YXR(Cm33-Yod{FSIkZ@fdw}kf!dFmy6+^1YlpM>l4e4~W-KFR6B z5`L86(n&Zl3(o40d$1|+;s@KNYOHh$7LnWFPalkjxG zCtbo12|gJTuJg&1a9=mCC`-bR5c~IR3D^1LN_hW2IiEZUKVI;$Nx06ZK*Ea!pT!b> zj^I-);X0oR3GWtuxLd+!2tM@^uKVFW3GeUV?P`>8yWkU+a9y4z3Gd#?`7}%TBEhFc z!kq$dm2ls4oKKsC-yrBC5`L4wJ0(2)7tW_!!j}vB9tqd|!(IswH*-Fkgx?|f^h@~N z0w0j@2)!skKjOVS-TogC^l1|QCxNF+c)ytEGbH>eL7yq%F9B3O~Q8wJ_Qo4w`;M4cfZQjQ!L?c3qBPRuD8oA;l+ZFPr|dl;QSjU{6c|; zCEWKWr*D$*0zuy_;kx~_NO-g0(>kBedq;rnXwI7b+h)M^cQ_~`V!$UE=sOMgX$HL8 zfS+x^dkpwR2E5mRUuwWL1D20YJzPc+~*18%gp0t5b21N~wHevAPx zHsBcsyuyGVYrx$G{5S(%Z@`Z?;64L>f&p(d;3pdJumL~GfHxWNlMQ&Y0naqxEe8A) z1Kw)DPc`6e2K;9RJYv8n8}Lp8e!2ngHsEI%@E!x6Wx#t4_!I-K8Spa=c)tNZ%YY9U zaN~TUi1AM2`Wyp&ngO3`z|#%*Gy|Stz_Sf_rUB0};8_OzTmznMz|S+_xd!}v1DC14fqTLUSYtE^RU~1Uu>YSH{h2TaGwF6X}}u|_+ zY{2sjc#{D)8}McWKFff&7;uXL|MBrh4g9EqA2sl!27c7Qj~e(<13zluM-BX_fgd&S zqXvG|z<*N%pP5cNWb^M&vjtPv4l0T*5b=x{?6&zgr|}CDgVXQ$T2Tg1Yrwyzlk@OI z=?W4d+B=A!({83T1@EHWEL}@!3PDCAEPVr|DfAd^W$EiFO(DlivYTFlZHQJO-G(E^q}m(mnkjOMZQnUtoGVl7yu3A;qY|(qk!2p~PtaHvmo>L1_vhMtfQM&|#!0bQtYs>3>t2 zLWa=@OMggd3Kd3MS^6DHQ;0Cy%+k9lO`*YPn5B16nnHq6A4|VTX$l2K-7LM4(i8%W z7PIs^N>k`BTENnOr!ivV>SO7-l%^0})XmbfC{3ZcXfaD)L}?1iMGIK^TuM_YE}F;EXHuF% zaM5g*K9$lGdW&YV^l_A?kXtmJrH`UCh1#MDOOK^Ah1jC~hgttqnnG*QUY0)e719(^ zi*~d0zbQ?jv}lB-KcqB;(4ws@{SKumbQW!9>D`p3kXba$(mN6w&HXX*PX zO-C0|1!-Go<{JQPp;VH~HeE6HtqlKcO`VC%l%*(}P(Hc>nL?gI6g6LEF0`v(Y@kWQylA0K{R%T)x-B?* zFzmy`V^=p*Tk_38YnmhEebXAO9+k5rU+rH@vy?;qz^3jsb*k1kZNZUd(>4^s?1X2F zTK%R@wJuI7^8K6B9cu3q^Uuvo&DW8L4Pd4ykmnT=>1w+=NOI+?Tg;2h>Z=POQWivV z1ik4tHNVxN=C|2`lU5zVWAuE7ImNY zJlO!sdfo#2`drT$wqSl+KI&JkZKxb#?D&#+dU4^X-8>j}qcW-iD@c}Tyt!!UrZ&hESzl*p0k?v-K?9EvL2%>-PPlh4+yo2A*#Y7=(am;s3o_SI zrbGQ0nY^cM!Re!@l`7~vk(aZB(QXDU%wsSd0oD-lUXdSo&ojpnJlYbp_T;Nkp)SUWu)}F2C_qy@>FD?V|=duK?Tgb^{iHSU6 zV~Gr&C}4>bcw#Y2(AtMG#Vj$ICn{JXiznPHaW)d4nOqNA`0v0Xo>|W_X+14BxptSE zZrG0T;0p%MXUO$$m?jjqA*UNT?Z{~i26B1McGHBVEkMG-z$HLH*NIe9Fp$Sm)*hsq zgMl)n@T9@O2E)x{gtu>|4uXX&-BdnRpG@@rC40!(v6h?)qcMc5jmE&|*_i!+o}3aR zJ$>_thB`N%q4eYg8R@gXjeae;K)TVz?DOj?V--gE*OGk>q2HFAo?ONu^baSeCnXJ` zuTD;X>Mt^J?ablnhu9lgxAyfiYOK-T zu*yXs2K+)t$k~gvP<@@&yHMeqs426<`X+7lOjE4A+9{t84$^Qqfv<ngbf z+<72hfldGGrV!0CLDS2;hPZb4{A}cJX|x`W*MK>L)$x~dZbE3UMI{0gWrZw+b&>^?)kDvz~YA6fT zj!=j;cMkQRpV^*Zqz?69{BaPEwou@FkOcGLPVX(yK3@p`Yq9wnsKV561~{R2(EhHaoMp9+sn)<@-(N*87mN4%B2v@ZG)T5~GVga1 zhI+i*w2MipeN{4~R@PrkuIqjQxn>L_SIp%`zy20P)ZY_87p3tc`uiz?e|ZLQwgG}2 z{J%Bdjhg=h=ewQrVrKE}^WA$l^X4CgW=h@n-<z`$Yk|K_`2YR+?$8x6 z!~S2*ceGzgKHojEbcnJH^Ihu-==1HL4I%l?`EDkJ|9HZK%tf{QoGGtgc-G8)gS%)^x96GG9Kb?R z1^(nRkcTjOuCBELsKkRD&Xv65QK)BaCjQYrU6R03j7O%IcfDl)rzXwMBE3x*F29hq zpS7L|1`RM@H{@mu*#UE*L+xiu;m$6(xLhYbthJ6{bvpN5tJunhuo9Y+YmZF;|6*EN z-;Z4IS$jMsRdRQ+j8hHeDhtc;)w~J0%uT&HKk$*q%%YXFgb#)nV<*ajn22(sNG&a5 z&vp_)C_oO}U^oxkEuA?NSAB!0?xYQ*t;6~b)mE-rd-1H%Gj=ZTsrg!;iO0idqaJ_m zDDO$@(vgWRA1vMV0pFp$gI!)av&67=Jm`WH!6o2ePhxlu>%Shag$PtL?TTopb~5%( znP9B-UBmZhsn~m&I%y;B11!SX%=Ehi#k)gxXBLXDyv|z4q!e}7Lf(F~>fgi%Eu`lG zdPWOBSMvw7jQ4pnMs{rqYfIiC`MVDGpl0HivxCLPUl3~3zQS0d{o@g|oyk+Qvp~Nz zsKn?=wXkr0!U7W(CoC@%V4$JF8K^u$mE(6O{nj3*4y413pO2)D=|E3j_zD_K;}1tM zpV=M-@?QPABDM zQeG%^*FG9A2MNgrq8(U9@bqUzEu*DcF6Xt>Q!RZJyp}w@mb-W@MN|uCnae9V1=c|| z*r2IV#j1Lv0W@P{bHbze@;l2P#5SBYIQwD4UPKf^0k5 z9~_w79@o!@*!L+7Q{1jV!E9`?GH`gNjxzD7G#r_0g-xYNPy5eeXPFmFL4qafW;W~zJ*fMypv;$ z+GC9Duwd#LEMccnc4+)=BKNj17n!DBAr_ zl5$l))vRD<&3P}ehHSlt%Rz7UZyW6U zOPG{EQZ^DV|IEX*z?cF*$AnBdq4DEcW2hxi?>laTdUt@7v=W#GVodg29`kyn2BW9o zA2y%l@Y&s1P2ZkajeXs*M|OK7p%EmZc1xMi%CG)4I7r4Bc$-#?OLhM2d>2PrpW}?K z7!t7)zrX=HWXADs2F8c2BXu^l3O_t>hlYGy!_QKpPeLW}?Kk!>Z6Na*0)%boQ^lCZ z;7&*@nThE%DW77X#_|P;X(m7B&wO<&O`%`Y@f?l}Uqm}TW78)c39rIYa(*X2=q)$< zFPS_7=Ig!19<(a?Aoqa3Vi8RhNmX;|4z&vwy4HlkW|6+~7=ZRe7q791B-E#{zOat4VOy`J@|4;o_aniy|aoJZff zg#Oa?7gwG^b2ilrr)E;|9x_|rAl!6fO}7UpP9B_oD{^eX@%Q4FBRIaE{YqVlUmBgj zU6a#g_un!(%XFRTP8uKfpl5O>^$YCl7q;N+G;7WQ`wJ70Q$e}w@Q*^f1P5+O%oFRl zV@5a89&J;*Z3lb&y`ydZNV?5`$h3UA9qrDtrF?3e@`i0jd+lkqDebly+iOp>`8R^< z9en9xCshJ&-nACfvvuix&z6VLMX!*qpz}`FQIqc9O>N-)l@NZUj-n+vZE*T>MgwJp z7N(=2E+kRd5lpRS8RKj4OS=i7sKu;}@upnME>Xob9%k`u2PRJ}``l>ZdqI`?}5h4!5(!}-(KwZg44TtAa&|<_?PCE=;JV%xPr<5!ZNJ9K>v`E zabE>)qgM7NyyZ{Qc{&wkjLQ_{hJYS*gRiAAed((Z1{f>0YzCS=);? znEi(bO%Fz59&5ZR;qOIs4-TFxb}w7#=7ck{&=y+S$tIvc7w)4(*d%1K_2vhjq7QKnH|XFs{=Om4GS%RA6V3b+L<6=T6>kWW}-O-^NE;DVi!X)a4{r~ z#)V|#o6ZKFBL3&#H{Gqv5WZZj{ks+&>COMOR+@vB{|m-bGkR-wZ`rdDl+YSA3y1s@;!Kv{CI zhd0B>AGcBL>S5~0G)M59n|6_#^^s@t3-q}T?OKSfZVK$FpG@E~5>Xfs_7dS2C?u=x zLTyB|@=fiDU1&`@D^GK9X!_+8jjM(^3v!}i`u812p-XEXh`bkyTW?lF2dpdMrcu7Xu_oy zC~Kc~3`_vr&(}VFjfA)1HqxPS-t*xhQSM^^asO&(@Mxf(mr>^$96ZCf*8X-JYn9Pn zkoymjdmnQ9dgG=b>=~YM{CX8L98$c^QR+-r3!5CO6?dz8Eo%Nl_V)Ki+uD1Nuua)) zQ#abwcj1d@cawUp>5eJyVQyDHXzv{jL2RYns`U}gjXWMe$k~CYGq!Rsv%omK*ADB; z)ce|r4oKW#ZJ`8wqek*m!48l2{Ebv>sVb$7fRXfVV z_TZ?=beqbe9zvgC)k+1!;G{=iWqpWcpAo3uw%BZU2eMECx3TuwRckusi@DCfszu3H zy_o5ckrA{$Vpgqbo+It*(oB1YHOmJXgVtuVzaz!&-;!tdf9$jQ`+VjZ9j1U6U2F+f zH(78UEgxnUTpI5CDc}FSN;#RBRz{fu_k)Esrwefx3ekURQ+GozFPw#zYVV>~*|6Wn z@7L9rnm2%2@#LZ9!KDugJH%bJS)qyNQInxYyMJp6Z?id=pUa$R%*$IzJng6fIDj)g z_ilre+@dw?B4=-wjC{(76E zr!yO;_&v8LtNKs#Rm{NojJ()ZYK1+88}om|j0ZQ?M&-e{nr-dx9ARsJdoE> zv-aQ-&`~opi-R$ch zS5*D0?U2mBB_lQtZU(@cY)6^ySkr9&>NXz@0n6kbFmAHX*lY?siN1zSTY^sbL)DFa z6ZQE_yJ9-Pj2TC0W-|n#efXiPsmmKC zH&PwMb36G>q0~E(Pq>fpQ0lw57)r@7OA@yZGF9#OR}q_!QI|%4v&C3ZF;#>bnnVrF zdJQgK!&Fj5a22|$VR93%shXx3Geo+Ot421~QLg*xJ2aI-jxc6dLF zrI{N3$SRmVQ3&@8$%>&zaRyT2EtJjo+VRlGRjdqVvvO36T<8te!Zx;*ARU^t5X@o$ zgbxuUW2|yGD|DF~WV{SQv!I`9r2o54pCafP$+1j69t9SWiQ9F;9yE(dLA<`f&Y1I# zNl>5@{*4nt(8)0gP-GX1#E6gAiSICSZ6C_@!)>Bn^ry{Z)ju|raIH?5Z6v&JC}E>c zINC_Kd??}7I^kRBd+MFg`1^0rn~es|N0C^APSuH@=EPj(7Y*h5DO>^8QEUYHXI3B$ zRZicI-=4x3P+HMayUC%WiNr#iA6&$KhBh^QW3`uF=5goKaM8L6GZ1sr&=K%Wlz;NL z?VgU*mEc8;fF9aLvCVz!$W}L`6LZ@_Y7NY)2)y7G+S(}EWN@!(Ur3Rr%a9*0M@!;( z6h+)95gb?`_?d`bXi~xT)E*ikTJ<7ib)?RvCgP_JtL7lm=ZZ8QPmVtx8GropC0+mo zAH*Mb#vdc`$0y^Dzl%TqQh!AI3{6A4=`@nT5Y}RTp?N*2E3sXfdb=)siM6Y~B-XA6 zNrj&HUOIXbARlr?5e}`W?!lC}l1R z!WSKg-&fRr-^KEg90&Lo7s53yx&8#k}|^l2?1FGqJqTkNP@JbuU0pC~0ZPMIZrNP&WB2)UHN_*iJ_3C$OES@ytrQ0^BhZK%aMm8R>Jp zh-k3;Y>E7G1{Z2KK#h#qk%Ac;Z`#NhpX)fEcaDtli4O<#e`vb52=a$!+CfKxG8YJM z!)RfMiHX#Mo0-)SevfkFhD?P9DIzP@^dKmqumY+Q4nZG zy8yyqBFxfv3rjblVBbgRUlutr#Am6$v0i>_f3>g$V8er#XEEI!xH7J+{Z8%EtVo<3ygDxzZ{omlY;aD)o3Sw$V#{*s` zvITaS?ztPXThs$%wwfAl$D@Ddf~{;?Aw#v5cEU-k{M7%}@w3|pPqwvxbMBOHptSMU zz=7M+Y^7UmGrDiN(pq^yg9gkg2l|eJJzfm?m_KX^oQis|%j2OPXf?w1fQz!5v#^n- zi?DiFCw3lyIuSnVWW5(EY|ZJ)Ibc!$=}^59GaDD^y;kjY#%Sd+(LX>+er~L7+9K-p zp1qa^Zmib1yw3+7Wy!o{;;(#LTS|4ibFx{!K`h4KaCrp zwj@?hJ~|GsKE*1!4?T-9@~`L#s6MtoI1BW!+2@y%86udWjfKUD_m-kAP#)}w_F=v{ z*h2!cqwRQYbbn_2m!A=bNe_ZUbQ`W|#j|N$4o!OFORO62%|(zDa)Z8$(>sa&;bG_> zBl>3qJIdXoV9=T)IE0?S@k%9*ZElczhs6;0TsY#?o}c1bIS}E}}hOaQagl@e2l`%Z&I&8_a<)Ql|yy|7|x)X{ab6KT}H6*@vRvL_(4?zz847b=SM+0>7@&sn85-aC z540&XDF;;Mwx3Wt`Kk)72*aXV890A{vv7i)Vhf%O?z!u+`M7_@9I)wYt|cby+&mCL zAj8z~Jj@1p`VNr-o;EdgUL*l+Bh)|hLZFJNVG1vT$@7jO?5c-lH+ooh{fL``-R=}H z4^0m)q2awxn|(Qwfe7aJ9#g|c%A-R!oKx)`T#$_nL`e8Od?q!`go9zaJ^`s)z|ep) zse{w+CzS37+{BpiWAaW^O|h2XNOma1xIjp}{b>r^2Rgg~iXaS=Y*mkTuaz-|l;a^~ zq5^X`S4n}>by4^ZU5IkBE=rmZB{cq8#DvhtbeWq8!E&hisfQ3|H8s2ip&%uyKA$KP z`fd%+ld66k<(L)>Rr_cD3Mkm!8)H`|^_}NNLm^=X%Z6oDK&(7AM$Yxm?PGIBe1A%Y z=K~oep?4uWw1;q?fZ1lWhz{5c+dAxEd$d)^mKbvC98mN92dJ(F73-Ow0`2=J#|`%$ zP*>mmeBDS`Kgr#vSDopdpkOm?KD37-s1Q3ans%`y;XH_zOU%4N?n1S{ec)soa1Nd zaFCUM!3S7#5(|1VvPjoi>_Y})@mD;=SX>!nL7@ZfRANC-Mi$#Z!=!kIv+!2OiXRm( zj=`$Ei^|r~d84uTJ$mtgC_Xh-eCKm|0~EyRv`1KRir8@-Y}OW_ET%UK9Ilwn#x#6< z?I9Gi#ST=vHfc`)2rALtaIvT#wcqpfc;=%a9pq^`(PjC+;A!&AC@uFdHbpk|pt?(& z4Tfy~*yK5j&QNF%r0&KFasP8zb_?9-bc1J<3a)Iuj5#Ly`iZWG|E1yedh);ECiR0q zxkspD@7JsjOrV|>WXi$m=YTmIzsV;S?g}&bv zy=>Mn{$yx;?o&`gXp$LZ<~HGOa2=BJphKt<3e?;vjW8WgcLJ{e`vV!&264}IxDpB=L~?CCUM1F&vE`ZQqp=d`5P!#k|+Z37_MH{$UB{gV5Bl1g6S~!<;zZK+IycY@R<8#NH!`ke0GW z5{XM48o!vAJBgyo2xGI$W+INsiXOzoYYJ>2i#HUH(xC|11D%&%vSXVGqj0d05a`J{8n+kVr%MaWaAlu z5%qoT3(jc_1$}t@!B0go3y$*i9qb{H`{#w$DR?6vL5BR&sCiL-Y45zy4=M)+O_cdOmzrCpm~2I~&f++rs+(5(ZG$LbT``?$(V z7rdK^7sl&vTcbDS?Ir3)+GMlZucLU;5?ug- zAp~kCJnj$LOWV;+qo~bS)7>MwsvAFJQVSo#_PiAbl88h-f>ZHk8!m5|Fps43Q%h@$ zMaAFrjKCb)$Zt@s&zl-9q*cQ|^KS&08a~A$V4e{%g(66znYb#_f_X2}XJ>Kdk@=;# zhe%_>%uaVjYcsLJ;|3Gq`RW;V_3|{>BERiunc;o@*5#CMLx{)j@4zaIudQIyMf-Oc zI~|JC^--EaC!Go@^ubD3d?@6@GA2kFmMOis^E1O5fsfc}^3!EA%f;SDObv_a=`1gr zj|0}A^C27pH|OlZmsncL!z>zrki|aj{?)=DCTnNlF__{1YLut<;2s;!`)s9~VlfD^ zwLF?0>ffa-8TZeuUrlNY{0sl0rbmL-=SgGc8CzD}!PGX7mB*dZQiQQ+ZZi9~;8dLJ z?fh)6H^TGP3njf(KX1C{A~t5};9(sl@3!Jf$sD6ytU~*)1v|%C)t$I2!0qDDx3ujG+6o!k zM#r8sOMZZ+eaiFr$@N*tz|rs)ZX4IJR;@y-So_Xeg;uRd+@%!|Rli(zn@mWf9`#j<&k9eU&AKA08b^AE;>tz#q2khn6oQB09UqWjC= zcRpUI*L$MY@^nJI|99r&ov2i-2U+p@fB1Zii5R|3FU8Mr(-*!?EPlqf8f;%V7`9=G zTepUJ=N*{pHkdKV=0vpWe^5*tdk@&K?hJ+tK#LIW%5mB~tz?ba=7aclQ-91gHO9;e zZCF>>&ux&TYg1txx#R3n#DC;8R_lcPs>C!Lx8i(G5iQCc-oJYk`JqYl9;3MpvJyM( zPoVV=q;wq{Ic+p{A$~1|&6|)%Q8Eqk;8Bm~*br4=0Sx(m_gNHf!4Dkjv+1v0tt{{fs1(dd|tTQeKYNj`-UgU!PBWrhikw z>-dSW4)#t(=vQuH9-29m@!4uqA8b8{_qpGj4m8M~nNYJ(V==GscoGW752~LRO>H`f zM3|MX-OFo>h}xQohu)$ySm9qa^4cCJj}>+;YFj=`ZT}{ng;LjplGnzIre2#NYCDD3 zwm{T&uCX@w$|(G+W?oxKVr^-|)JEPSYAcJ?#*3!De2l2=ALkLlq*oDlH@D4X{rLvm z5ZaP-f*!j2;2-VpR3NyjQ|tQc;9y6f4O*q2AbY~iVKSctXTgV3C}E9I z9OMBMiZg@lXxD=u;^=+9MSuQ!KJU*uJ0hcGP*}EU7t%TkJXzC;|rLM=T zTC^$0+)3d>-aPolXkiP|(8?^7G{QnFX8~qfNf)idF8%$>frTcmzL>M-udh)rq)uYh zzYi=OOdm~+ruhQ5OavJ|P{fXiEI9A+e=`P4n&+4QJ5tuN5s~5T(HF@QVZ7KJ1}f%|6i!({w3ybBJgngW)d=CriLDE zK_-u{zDn^lkW|A~()K25?Ry^nG=jwb-ot%Qi|>PAOO8A9>Wl2HDZG2ZRl@ZF&G-(b zKr5bFp%*MU>(e0w_sf~Q`?MWs9m#u|*7)a`y#9d_LWaVeGWPjM9iDplG)Jh@?q z_boxeK3tXPzZ+^i0rY+M%ICqXf8ovc;@uzKk4t;t)zaZGUZ{Y2MW=h==dKz+7q@~9 z#y{GlKKOH}Bi7eBJB+TLdwB?kx{-{4<*=?p!+i%r@c z4wbz|q=nFRV3UKFR&bx5m#5x#1X;cVE9(u9GQD}G2iKE0=FnAm1MUOuGn9lnJyQ`` z>_I%F7jad@SJ8~%D&zg5<58S8K_fgb;O@Z=Z5&JEv4i>3X5Vd9knlQi9!?H!5D&%o zOTr$6HIVg_HPA~?q!9mRA3V$mP_~$btgT2kWgv;)trPLP!^-Y6by%N=e}!>x#f<}N zw^|qh0d4Myut#M1kcEph)^6Aly%Wqn`1<=tP!+eMJUoj19o~=d@An4pG*6@ zc>HU>Z%97gs(DdZz$?sk&Zmp?Se!FS*M+#*6K1BLvx8oyMbBKeh8Cu%uAE>s6ZXL0%iZo)HwOuPi zx4|fg(OS|vJa9*lF?6U@zpa$XgS!r4XuO6|6gES7EquSv)>sM?E%}qsAHFJpN7{|~ zzZvBrW5%|O%?cy~%#;~386!p?Z!&$|g5RKt?T}c+50fEsjJn{V(5^~2{f+M2lJ4`cl}8F_=?wXD?cCSv5{>9C5972S5vXr zn!v{MiqV!(#R$B!1s8xw?V(#HkI?+o+ETViTh3ZfBG^W<_dfGWF|{(B$0eEDfOneI zXujHuWrN=KyHoF~0`08-qj%M-7@HW9R0ZBb4CE5aAd?4@iGhg^{{mJw9^4JHVy*8^ z7~>RdWpe^+QxYcdEr<)7XJPw@^7lQh`)ByR2;LUAsO?r2o0<3VHcfh0bQwI>MEgX@ z39B;uz9lWrn8y0yZ?F~FzdsvG8bYa~apTaczQQBT^Z`D#-=aqOmvd+V>Fe2 zVDvCtSGWZ6^9J$hAG3;Y*RV(zrgdx_sGBI}#R6WTCwUV0#<)KKC-+Sl16F8U4#YP% zeXtXp@@X}H;EV93c+&@~@+Vpd0RM6Ybc}nB{0r_-3PPfu3`cNWzN+DhBF#DoCZHGC ztLJ#(55i*wNBuzr+=5=L;C$W|=bwaT~Rkut%uSY3wNL+d9W7kXD`s}e5j65hB|1f)bosWFuz6Zrdo(K#BMCyiTWls zT8Vkz9~1gpeVu*KUaLdTs2lNuGA>e&rRll>?6D8|>i0BWV&9i(=qkj`YHI^FUGIC( z#9nk-&H?px?a_yL>A;TqR4S}!n-a@il5*?;|JD(GJKH{lCB^x({=>O7j;{`*ZHKjs zAEs<{F0WtouWbBy%~u$I0h*-dKW|re<>UQd_#f&nKAouTZK!Fm+}!?hn*Yo6TTe{p z2>Pg<`0BeOlpi5CfL@HqR}p5&Z^aki^U%L$-(U(xW-GoIigyjm&3Kt^gi>x*og+FF z^5fZiZq`vbk-py(0ith#OR?)Ay0M&&*$d{@4u!mM96lhA`VXgF){M{o>^T}`N>E0p z`|QtNyg-t=>$hZF7~6SicC`@yS<`bO(euzOT|f3<3Q)XZXV!BkQKRGI*|Y|)bt2@M zk4OpL+oR({N9f6P5aARNSCenwuR#plN>0aD?=yq>85T7}Z%E+#_c%+-vf(v8Z3X5d z{{YN`u5x?Bm?F4~MW1X%+c77E(HqTpeGD@+6^{E=?2YRKSiML74ltN_&(T_bt53mi zinNdtpAOZ<}TP3KEH*DfrW4}*RhRgY#T}o5!%zn+B3Q_bQ#h(xXR`;qG`=) z0?^V7yZQzin-$E@#Qp}0M>gS54r}aPm=Sqn?euz~b;{>-5HxZ%5#cCQ%*&Qw{>ye1 zudjZ|KO#Uo&P=_DXKGuT^d{~SO(d!0CR%VtKn{sm6yBirsjpbnF8$MEw2i=b#vt4N zn6r2L{${X({J87!29vl4JRq@WGRg-LZQvV$J1_uA-#Gt{3G+7F|5ACZVr-mZS)*k8 zz{yLnpJjUk+9Ag6=Rc6gCgeFLE{qeZ(Px(^Ih5?!OFQOL^aO zx-GOK4Wr}=1b@~Z36a}q+h zoITovJ#dVK+Y!G)WiI0V20x;ahK;_mo(DaOpe~AA)Nc$+>sHSd`N1P6Lm+zb{YxHH z7HccTIuI#-=GUy+Z;d;8k3tyHdk$Rbax{;xzZ6*fIA;%!;}~MS7Eq8smXFvdUf?mS zn882AiDwzBhX}=A)1Z?ThyVabpqN%JN%8(2HH<=eVA@~ta%}V<<{$PKTB3XKB-v+z z|I`&do>9c@m;2ZHksaHkCLGyegc<#&M5hT?1NkYOerX-PQ&+T&h5N+%#QbNlM|cD` z_Th{*?C^}mQUfo=2`_~{#|Ha*VJx~zE5bmesb!OAG2RC1UPo&Ire*OZ6#VW5z;^ib zTYT599NA;BlJ85=PDUTYDZ*{TEe~RE9e7W#2N6WF3d|?k6%U|}+!8&fi{67Wjwkv) z2?@kEd+%#6fhSEKM-YQNMH`3mhV{PCw=Io12$R-tQKRv0)tmftDC>?xa>Eq- zqjtchq)hEC6vl@rnl&eJ$@YI!;(36q1^^g(kj+Q#^%z<0{PZDLFJh1w82eGv0x^zJ8Cz&4Cy?xND|B%bn@EM=}mSodZyO%Z(6L#_Wk5q23(e=_p#8po^7$Q)QTF{$;g1WAF8>2%k3lw{?f3J|X`(+O&G%<9n%Mk; z{Q|{vk_QOSK{8_~eYcy135jd$hBzPId4_R_>&h6` z9<0Qnne%Wq%bh=(G0p4g-?$5CYPbjKM(aQA!O<|ukoBKz=0{M6>=2g@^ufsf4uVzH zST-h8WNH8SgtRyF#=F776mZh0PyEEv9T1x){uA`8uA@K+lfHrDrU0F>YcJnTjf?&a z&ul$zqtR(zPye#d9x(1_5Z`1sCJWFq zW>9BpUj%gn{-OJs|7twxI&MauSUhPa6!zbZC&lFL#z>%u7u~4m@h?89RQE&k1yCG& zZ!#hN#dcogtoYE~^seMn!e*|wZZrY`+kk@U-B=S;?FfQ!kiuSmmn>iUt}}m z7p($qY`x{10ru(?x}N+}_4RcoG=yGqfU@aZirQ8Pi3{6fvAFU#85C`{kb8{h1c*Ez zD+P7si4a*3iFFsC4fXbI7Wx(S!F@piy*{jM3H4nyw7#8p>itUf?Pb@@_G(YQG)#Rx zn}*iMYKYZG;WcCXJ^*v6eWeNYxkP<4hpVsRJL{w6$ynd=q4j+hFt%^-r=*^}+O`*m zY2U!c?`j`yxQz83F|>qa-$UdIzgekOQexO8e1c^UU_$EHTAl@mIM*~@XohvYdk3u$?eh9`8>L;%EeMj?lukIK|j@}m_$FoxVhn?TXfVXjce1J+Mdo4|< z*CpzmIb6LJRIfh28D~;bIhF|xx745vw&z07iB<{i88jPb{I^W=o`@)}y8YlA_&SNT zUDs`FzeNC>+}YK&JVkryc3mH|zLsTjeLT9A>q93+XEdN1p4quUyd&J#94^%5qt2j( z6;797vCqWhPkn={bTnsa<5};}a{tqywHNmavV+$5`m*EprCppL&#SmkO{O>r^3a!c zyMf^Lh6xe>0Ihoab|P2%n}j9|xkFgR7p$A3TJNzPed&5BW6@94p2@Evf$yeoY#+f!7JM#>F?M%&(C7*qIG)OINE~k)4tw-ev*Xn!QNPA=Cg>- zUNdNPNA>Uj?ZkPOhCex+!rT`04-T_5Du?_3xavN^7F?wb;yY+!dzPozf+x;c30;O} z-iaw}9fk?_w`{?2nuflz1uxbj(26J$TR&<4%?>BMePi2 zZ#_=gW_)nlhnUjDwXd$|ivZ*Pg%0cIS0X;pOFMJeO1HfF_OWq*?*$Wis6ItINc{Pou9r}r-hcXh1uSoa^t}omzoGAI z;Nu#zU|776_qgZqjOzAjOHs_ADd>m4{fh$*_F@U{k^_Tor;@csyvHvE+ano(l@ zX|n!6oun3Z7cRMDBGEQ~6RZ8~SnYe4C)93y4>RV!O7d*N9X7~=vk^pjAr(&9@pT*$ z3rBDkoJXQ3m_3S*MdOkhnWXm^a|d3Hz>m1YPhSBb`$ZJd7TDvVFB)sZV0^hYs-zl|3)DI1O@-Ymn*Q`>RX z=tJn;)Bx`IQ?>4qIg$JsADbGyNaj!Z!X8>RLOY#$mR_{k#6NI~bH2bw%ro|$8hr{C z#KvdiqVq6j+hLL0Y@w@CmQRC<&89UIf8sk-zQHlevq87ja^B!H zB3`i*{%+{1LEj}uPxGX09d+I`8@@Pz-}>7c_lbolO3Pk5Q@i#4MH*bMV*qlU1}E$?4P@U*ws$)e$e7%DHp62v-F?zfM`Hs-vb~J(MFIO)VEktGUu|@U?#bAix zOb#EV^U=Fg_?BM6_+$HNWRp2F`^RUoSxvZ*^T}6WC$0Z@&8ww`8ZhZ>keT(*+iB+= zrZp|$edEteCmllPq|tQ03F`du=SL0v|4jqsC6!gqvP_RFbD7hVX>pd9c&j{ffGuz? zt8~>&EypA2B$18?W>=Kd)Hti`)$XdP3whSFJ_b)~hHZjY;$$Y#5|Rb`nqE>C7< zO{FKZ&Q*G2Nm*HKX1S|2b6Ks+>oz0htizPTf4+^r&gHEwbtV@0Hu}mMkF&PCBwnPj zrsVo6CpEsjvIbH)Ju6(bH$t*9XT2i$W|o&!S5~cLBG-A{Zda|xiPnDadXiBpbxsep z-sSdWO+h8p4Hso9m9A+J!?SW)DOc|_Z%t+Wv;wEIc6P0^%vs~9EUBuySQ$z;r0Xu8 zJ9qAsxw;;Q@O8RfRaMg*l{Mb_g%HDu#e#l@&<$7raCAfJXY3Tm4JFGfKnF* zLaB#iwa#jn$H|^`%S0QO%u=dcr6pAfI4odMWwmpW%Tb9xP5@?TB2*DP<{$^zH~rI2x@x3cs` zb1BP265Z>pHJ6q;-5x6k>hp7(^Mx<8SAX9GgL_lV~vzfQlY)3+=UKXG8cBh z+nHZBePQJ?(dkQO0l&djS#vqflN=PbKImTJZm|?`rOKUt;M!>loS}iXt0*kV&sZ*yKW;Q)*MQx?WDdi0_@9_C) zUWo@|W1$DXFnX$1E+8{>)|5KS=GIi^J3SSyGQLy{nYWDPEhV0kWwj;M1tm1mnQLoH zR!-)e6LT(R!y~9JwXj_NgmLcRNy5LiHgq@^xLRiE z@ka+6Ym~9D)a7>CX|PbL01F;5u5?$TYgwU$6qSnsdeIobO4m7;Rio^}%A1`^HKxbf zmHNzB>hfX)d233lok^`oHzCD?AzcgG;`TWVy%Cz`u6318t6Nz|%jvXA_ww_nc}m?y z<(O8`x|$d>7(ekf7G>*i5nEqqwJ)k+Hdw?AN?}%vg@6fz-y(fA88y0xhOU4XU8F8&-y<_* zI_TR~Zl%htG%OWYc{!RZAhLX=&Rtbm>g1xpTd-puU&wu&%9>@E6c+NH!?JfRI(b=T z9UQ1aI3&Do_zX>e zS6wCR=c>x;N{>=XH7i*|Re}M*Rx1psxF0)frN=qTTaI;zFBgnLpU_xhxkx$d*l@=9 z<}gxAlJKEPfYwa*kIoFaU=%kEmvW{COa zH&nT5mMPvc4E>sd5>Ex2r-!dk>=cr#gRenzYF*W=>EzsFEm%%~-YPw9aaEVVYvFqj z9TY?fd0FJ-*d3?UU5vSu65^3rFqwoM|8PEf9mDjc@Pg}5A6sq`of9~5lp}4%NUCy( z9$Ot)V-gT-+3G61o-)@8Oh)!=uZ79b{(zpDZ)R7jXx6O3rlqE`u3|RdDkx=@bz;|` zlyIjSwlNEZ`Sw9?2ir5~37%zCjh@5EhRc8jam853{h|E}2^d3T+nN}{nSfA2^z!5r zEFzbYe(BYttysxeFM)eP9vcG4P0Tck3QZ*$6HFfo+@x}>(xEjDp^7nLvguM&=d%7Z z0%$qah+ou;37kZKC1Bi3pXb7=Te1=*Jqw)PI;U`YJhN(DC1tb|i{698TJqJgJ%@X7E&Ka6PP)qF~3xr&*(Y#^x=|QsRg934G#2O$kexK16lO z@A}Q(%!yMC4$i?>WgHh}&Yfq^T+aQo%<9Uz>Jm?B1!lqP8L6Nttck_aG7)YmaXT~J zB_4Q}HJN7=omP4VVsQUYdtVaNo1~#IEsjvN3g8F#LGAJ?+6QuVxLc&V4BR$GeZ5_={v?)aa44}O+4jqP=k%Q7 za*5U5G55X&Ejq6gah;}oVEK|dG=7POS8ttBeEHkh!221FvsarYJ&rUT#@-Ro8!^px zV5G$~Gr-UW(|n%nz&D7Gn&y0b8*3O?4$K0Zfs?=#&=)h!-M|oVFE9-}2pk0-1x^6x z@W2po zFE9-}2pk0-1x^6xiyMe~_rkMpsfNub^KrenU=iP*GK!2BM z-UKv~rnw*J>oLu__!i$}3h@zQqiGnJ#=h3`^zAOagZhe$F%> z0*-zjb zgYp5>z)|4n{ib;s==(DKfnDQaU=Zki0PTU!ff-;1m<5gkCxBU?fi3khpbt0!3;>M> z;YXk!m<9%b8Q>UjKk<*C-hmn5nP-3p27tz+s6U`@FWNJ30=OUOe++uEe+}v23w7yz z=qJF5$KfYnYU=~PU1fBqHBmMyT8_*9t02~7z1rC4LG#7pV{ssntUi?_21DFQh37iBzPv`it z&fCDi4^Yo%fd{SwX2%dea2Vfm8U=b^Kz`^PIQMLXdlBUYP6DIA$U%%dz%k$xz`&2- zPv9tU{z8=JC6pI93`_zeKSsR*$AHh%`8dWYItMO12Y!1Q`2ZR}K|25r10MqVev0-9 z90it~i*yd5Jp;#p>w%G1P_MvA;9g+zXJ~Ig0M{pC)SaYj>3l&8{9S zts6M0=;c!&uJl}~!7xw^V-NoJU4`#<0Cl)E^GfT7iU&&uW*0q5${J4y|D&rIWB9x2 zYSX;Xu3ZZw&Z$h3_#1@%MJo>>UpIc57{eV zZ!Tb82Ya-Dy&v`z?D_IZ@sGg1t$=+W?7Iut$6?=7zE+t$xN~UkE#OS8ZRBV?PFac>()^V$^>D zdj;&x1=3#!d$fSPANEuM`v~mY3fT9-zPo^Z9QHj0?8ji=Tfn{meSUuddj;$V3)t7e zez<_WANHfL=c^y8(-GK9t~1SRtoo~+S6Ve#JWz5Ib@+PEGTm26yC2$$8EAdbPD0xO zZIcym^}N!!9&I)tO47`qZ5a1M(`eU3^*D1KG(qqc&>VzjKK!WL7vc%tUc9R$G&Fnp zU}-(ZvTst5pr`n}S#(hEDf*PgYYD162E~&`JP)F zPnXm5oZ>lvc*WO#>)P@CI?E@k=9Mld-!2=PJy^O7t{tbKwVt9CgjJrX3QKKa3}GL_ zeM`5MZXLIqqSd5b>RF|x4AdDcNi8sABHWDzvWEe*=irDLjN zO%R%PXg*}uEGJF1rw0FD0G{q6Q_y@I?N9py*T@mnS_RlK_A?z7-QhxaBzpg9dU5Sz#VpQbK(C92j>HK$ibC^ z8@F*(zB+IR!O2uKb$blm|f5Qz*Hazhs;skrdsRo zhkoG=ruk2nei_=+uHuHFlIp?Pbpxd=uY~`|59836cboJ(R`v1WfBH#T09*?(YMz3+ ztfeufsGfZ42kku>^8;w^ur$)Ibvde0jy5VsrDqL=p|S)K<`}|Eja8`Ir9;KlgC)U% z*-5Rwn$(8Ur=VZdXruY-Qv$`j46X#P_-FuRkqRi4cR zXgs8+HG(-Q)BI$Pp4wWyHP2e5b7j!HtORQT?a(*inyCGPGRwSd*f&QIg#3l41!Yin3b=^uu+ z0@|A`Eyh!sKd+}uRC=h-%$6NKh|Xr_F*mVK$mKWZ!O z(CmlipuO&L`n#UVFY^>7bdGdC&LCXJR@21thWb!G_k!yO2e7yUI7!)b^IM%~Ud(v3}#>UJ2LXs#ZFcE_KL7dszKdQ z@%5DSau1cK1QFbS8`g4gZ26~gpm-xmYCLL=iLS1E>4)}$o%Z`q^u-1(Y=oT3+zd?x zH0@S=Sm&r#^MTrd+3HG-6%L4DC;c|)`=Rg4(X08uDsunFDf%is-|$1T7y1LxmzCf| zjRz=qXyBy5(xKVAN^UPMUdJ=1CJI>Xp)`&l%>LU=Gj64UF;C~IYGAgSa4c7oUf4-b z>(?b%-=1k1>YxuoKa(<$&-^odV7jL;Wk<%SopiW zcm;mPrq})+r)c^9UQ>1Q3&|;8J{5_8_-n;Gh;aD@`MFyISQ+_9*ITM;X92-?M zSF4{_x{jg?dg^qg&@isbekS&u0?^V{l+~wJlU0}f3btRIn@5y=y)JvNj!?CKXq)db z&F@)p(KX@r;zsJCxTzd0ExLo$%TO57k3fGE`d93F)nD{|D6N&Nb)j0_o)193=U&s? zYUy#W!tGftMFjQ22l?PAv~#dm);nE$3+16w-;yYw`Y@?o5A7wE7Cu$;jG&r$QX2_E z(+|zm_za)&_($z73VS>3-?scj^JCS2^bKhdE$M_PX{+6hAWX@9rimsy)$TY>8o&0j zeVt`j?GD=wYv}sXLA69_Pe6AC;`ySbLz@rLT%}$YwMb0_YIs>lm6`So_aV%OtT0$7 z;Po78M^rx*(Cmli8CU32C3F-P$QK8 zMcChrKHy#-b!;zGALO?rbax`2q?JeN2QoL>LOHCp&y8rG_0a!?ehm6EA2iKfR$HTM zr5@vGeiTJZtD#hQUCB5D*SCjE^GSPtmeX|!7o{OAES*%eLuX3&c|Y`X|NZ!57}dvm zXosO4v*M=lZg4inyMa;~@6`BEqvoJHpe=uRX4<{bjzfE+6|Wi_(Wc{+)jChR2J-g= zv=@BEp{*S#xoWVOM?E#mrDaDoo_kT*H$i{I9``cJ@VZ^agR?(DG1H-Nom`m-8Mt*ZfFn8P@9Ez?pJ4)hc}=N%}~4GA245;ftGw6gmxU- zHp^!i3*ZN}v{LI)(BRP%KGLV4U+{=&K5gmcnu&%tH2a#{i}eJDDuw*K2l{=`PtDaZ zh9Q2n4vD!Mxm)}^2JPF>PF)AkHlXFZjvMUSx1o(ZYL7R6{n0qO2;URf1ML^AJnJ&{ z>juH2qskFo!qi^tdW7-rHO<@XVVY>{)k6`j)l&JkLD!#Gr^e=G9$w=l?I^T|W~e;` zZQwuhr9VC1IT)Dt%urhn?HqiEV|ux!$4lR%=$N5)5ZbXBYWG1~{xy5QMw?hRP%>D& ziE5;1~O0?ta zq`i^81NtoVuUUGu&1H1KY#u6BLszw@C`}>i)LQPH(5La8n2mNl^1>zTf>2$X_CrU{ zSfVtn>mGEi5D<45_Ho#!?tST+v#S`p2DG-JrqzvVwN|&s`5!?4{JMR;RE@7Pw@}Nr z(sl#Tc0hZVJ#DqV5yJY0S~^g38k)1vcU|Z^JyUaK8Mk?l02*rJyP;czhm`EhiIcuc zp|0aX%t)2cf-d7EaWCpDr6NGeg*Y&`a9Yp5jy0w0@~t3pk1}$DT0Fv#cT4)cPjUpotLMgKVdMuk(Phf#+%zJ_`F0gm=3? z-p2N+`olU6-yc(yCq5@UaKZy8JaEDTCp>V%11CIi!UHEfaKZy8JaEDT|NlKO)~wSU zX3ShcH<9k2fBc2EeVEbPrO%h}`9~SEoth6i_=yh9uVOyR7zu0sdIxVXe-rb!F^*lQ z^}~FA7o)LW^Y`-kC}T)1w&BT)eEuwBg!B15KA&LpMRYvB?0$~$(A=2toR4D(?JpJe_92OnU5n}hc<|F6tT zxkfoZcRTnr^P>(v#C+Dl`nRFLm%C=9?Y7kNFk{KY5i-_XY=_Wqv#J|H3$NrPhz|`4<^On>0Vl=U-<` zw`=|xK7Wxh64U&vd@lOoR?WZR&?h6B|1M7`RdMO?)0^%&)&wj#;uzF8}p_At2k&P-)Bftl>J&CieHRf`us8n?`6Ku!TXqRa`1lUuXFGL=DQqxi1`~Ge1!R( z4nE2Joen9~r%$wp^NXQ1TmnD&s*`ZB84; z&2MV{6t*Y7pwE4LelFwWU7BCY=fm9Y)66$AA7=D2-^S;ij1!;L@$BSti9f>pF!K`s zBlh>ZG{1q*W&8=S{ziv>>}y&dXI|>h|9#DGW?tqw=`U-3EAzK9hMv~^ zFrUl(&G(Gv?{e^=?`Zx(C;y1%zvJLD`!)YF=6}Z+cvAC69lVeE(n)O>zebo};NbmF zu#Wl3Kj`zx$24Bde2{VC+nQg^=NlM(tWWUyR?(|3xZ!(TeExODEc5$C#~67?$Nwun zmv)@}ispaIytL!ww>1A}hd#i1)1jYWz32BjoYecozw7hUn3wi5{GjIF%e>Tk-~r8l z(7{LU*Ze;*FY|%WbDD2(@Dm3#f0cvxepmBt4nFgrn(uS)e&%;N_$;UUc_;rp9nS*} z-p~AF%**_L_(xj*4D;f*>Z9^U|*;pVjfyJM}MWzM1(LWBMDKzn;%;U>yFE=KJ_u`rU9w^IMr8 zWXwLV`Q3beFXJTF`@?+x6l0qC1AH#?u@L9;1?DB5v4cAPR~_+0nAcY9!~RhpUBZ|? zq|ZZqF6GEDFL%Sw6=~h69MOw>K9|ppkUp5l=WlcP_wxBL-&dc(=W`g}$2h|FGadFn z{>@Af72)(|vpspTJ}BjL`h7KhPSIct7V85KpD$o!+Q_WXCF5Ym5sr91>rP^P598?N z`at*)J3`74xlr3VTl82D_4!9e(f!5LYW8BVo2jhK=k1;;O_!8smjBhcPuF^$$H{-dC7c|@-{cn9NsjE^xs!}t>8>x^$PmagXX8P8?Bn6ZX&En^#FALDk$I~ea{e2noK#+Mjh zXMBsXw29MaJeToe#u~=8jBSj4jN2LSV7!mzwERpL%_X@q5#0rD3Va@K}Bn`#YEqIoiot%*%SJ z=qqqA%Xk;Qu&Bou8D)Zsf2SeizvwsNfYO!qS>fq6e->(4t|!9ZfrEVc9|ceGOmIGT zaDW5MC)a2~J;Mcuzrr8o)5rOw`k~L;_#=LfgFg)gA>QlY&t!gqga0si%1?H!idLmZ zM_04n@6h)!zsSMwU>yW}9VP4iwAmXZ+~E3)>qGA{sSdCpRyh+{4(Zc{Z{xS^ONg! z1QP$}n3wfo;j_%kdb05J7F_aA`r6!h-hp`y@v>em`iq&LaOhi@mvwm2?_^%q?S+4Y zd0FQdew=yP7ZCn0%#U5C^LdW=4HG%ar|frd^%%>Um;Djp`k&bUtMt>O;)`1+JWBoMvQsUhPBspwH#d z=Nl(O5>JoT!7b{uiFsKs6@EMOvR*3uJt4vpLFmaX1?FSF9T10^Y>{#sOO8|u#NR0hyIhSm-X{^>tM#+ ztPeQ!|H1qf%yY9c{*&X8_4yW#hn~x+`gOl1=sHcGcc6U|FZ&MCuNN~f`xL^jWM1|= z#&tACC-bsz@=4~0m>=W)6!k0+*dAcsce56%XMN!CS>}_U(EM8%r|9zi zmwlGYnSYph*{>1)`^;xPrG*mDZzO)^S8zOY=5hPkp^0kd&t_isBiMaL4fC>(BmHy} z^RiDQ`Ps?5?1M;t9%X*`R-K@Ft_BW&2tMC<^)na#P4MLZ`yK6e4(`{9-{;`p%Y4?s zU&8#DgI~-10SDjBe8$12!K?PaOP34HNqz2O-s|AM$-K|O|CD*ZgFnW6z`?%{6Ld;9 z3_-@FUD0 zaq#ytKk48{!Bc(7e#}Fh{}-5-eWpdszsbDp>$Ee!;GH@i*$3jwfN=@)vTvlGwSX|| zn3sK~ILEV|;s%&oM9iUTKbpp3{)e&u>dSyuXCvsLy*&)A=_t`ds>p zpLwr?Z(!c%;IC)i@8G5VWU6)j@@2``34OkNeu3i|cEs}}c&hhfj(R-Ays=8hKgZCY zS*L6L90xDg5uf^@Kk8$))-QlwwTEx%^K~5066Pnfns^uUYnb|8ae;ep8RbFLFGx|MEp`HV!Z^`_P>s>KE}N4OG^LUCh~Bi@d|33{z(xN((SKh@%$FtOH_Xd^;%%I+2OC$EuIxVw|6%53zf$wB8_ zvHYw*2^)*~^7BscvkXj5-_I8!))VB@zaM(4cfaGhR>SeI!1@>zW@{=bd+ z&}TINumVhoJk8sk-`~E5^+`uOJDDHl{788pV1A76 zN8Z%Q7|$_puz#*${uuLtE={QC;o|TNTu>% z=@)*t<|W;GnD=o%+R5=e&3quE2`SgB%=;aFIN3*jJKb>Z`<@0~V( z|DY!R#{41XvwJmx%c%PN$*1G-vH#_M=j;z?KF$8$$oh{d{$%8n35IIcPjLL|nMtrk znGgL`3)OGZad-=O+!{>#e8z>p&xQY*3xCjs|D_B67x2DmxiO6QoJIM>x-0t`(;oHt znBu1g2)XdBF8pQ}{x#bq2g8+<-LFLBY=y6{)I@D*?f zwdbtEKONBLYlqui;`v|T^X2nV7ykP$@w^H?-*|h}MQ^lrr#9e?%w0`o2?MX`=9e>v zQ=MVF&bABh-3*7LiSWjb#Dy|VkZA#Fq*+XdNu1Tv5RwcR%XAZ7y#v4`fdRMp9@G{inG$VR} z>dYI7QqQE7DB*FN2~=pqGifGTdAw#ar7GM^Y`Q|tM9b)1vyFIxd?6>J6fwo_ z6RkXM8-;8J%}GaAusxCF`0Yu(E80E|yE;X`Z+b>jbcy=pir$V?oL+a_kVqt5V?{1! zD(#51^u?NbIx&X!x<{x~g(cS~aGg9}JR~X{4P~8PB-_@~TCl&ODXoZpq$$49)d~DA zY2{m25jpj^$8_R(1|M9X?Zb~I^<_MkJ7U^R@!bAoH#sjC zc3XF}-BFu%E8ZBLFE8{8`$StcO)RSWoYyuEpAxs@?e@!KTN=>c)dVHkLUWKXznRz0 z8QN4u?+2Edf~M%j*RgJTH+DXrW-$5qrq)C`_q=iVTJxHz71l(r?2hNRD;Fvs=J790ZadwKczt%bv8w^E zj%OdjOX8D+UBY`s}KU%qwhYq~lp z{ybURgjaCKI@apfcjpt>>jv$qkIIoxr?OO)>IlX<+?@|QnpJ+gcJS(0{(PozrR%QA zTZEVnnJ-fqBILdED-zMX6`N>l!#K}l4ZWv5Up!JcM3Oi2b|=wLQqW2E_DLPX8jV#i z!GMT+H*nn;q)_PU{?=GB6;E`j8xy>=+(A&cS<{7f+uBY|!(qj}VNWc|_fwY5x|PXq z)+r?toz;{VeWsg^Er(%bv@21)vaxz)GrKIGwYjQsDBtgWnfPx;EmH4VX~4UJXHU6ZI^8Cu;~i$t38OA_7HIu|u-nyQ1Yn))Vp7a)qt zhSk-T!K++@udHrfgOXNO)wxRQ8k;N)T0s{&v{ltpx3kl0uqV~6YS@TL8(V<^AH%mj zCAuGvS~uTlm&rsAZh|}Fo$(Y9XL5y5l$_X7%Z(w7`AI0AERY5*X!I1aqp?T%>g1kN zjG)#?q8g?n8=Gk)?<){q3pgFDueg^;DUY8aB`XjO<`Oc)QW;kMq>;duvgX1)G(W<$ zCyyzf=*w^DjVEBJPvn$Y;zM+eZP#NVNyc_t4#K@rT%Wr_Eva^zx!BXRmV0bj)La;s z2^o5gD2COISXH9cpqN_IKs%dinbAaNOT3H5-U6aHl9(QTdS+1Xt$j7!iOvG1Jaxsn zmcT)kC7s^!Bg#=vFRV4(x)szCD;-7Rfx@z!aRJZXZl)aqDoPmJzM^JEpHjao;;q`zfLetQ!-(X)PMeq z2Uh-(L~9Jo633SzC+?*|L`%DIn?p-d`SY^6WitlHf-$g!wnOsAfh8Kmk>A#YrQNQL zFoa_03jy55{{6 zRe@R~Uzu=gE%Ba0EZTr>9(xowMFlyn|-NE#2rPIjbMG z?z%yBfZVB^=5SwcTWpb=OVu=3(4yC2sQ3737Z4Rqdw}6^&Fab()#2)u!EiWFb3~B} zP6RL>(;i`}y*shFu{YJ&rdNFmI8jGuPs$#L^Bz-0v{H7u>}|a((X|u<*CuooJ@9js z;>~a`ZfEt%Qdln#?n5`P*i&2WSna5$=4mw_z20TpVGieaRNlI^)vVRTx^eG|e_R%e zZo@8VIJ%{)r8C}&w%3_+vt!S$&C|I#wW2E2gmc=btJ_r9bo#{z2d`RLxuU+xUJYEq zde6P!ywlB*oQZH4PX-BB*YVg=7hLW1P_jFQ+f+=?!p$qHc<>3=HZ(4+YzQ~j)L=m- z++4Y|p*m+sn(EN@T#JY79GAozyktPM+?rEEooY=KgP2+r)eX;5N~=>VpX#d%c@m}u^y=5XQq<`vP_$t@N^nWuRv?%H!Wy$F&+#k(#hZ-raBoWWC6dx>N!M;;FMCpU(X zoa#Q|Zp*l&tlVg8?Mii63}pf}PhZHOzL&$dsYRh2s)uina>-P8M=aOPdLmwK$rk&K zXa^O+VwRMv!dvIc8(qoncvq?|hwPn}FI>Jc@5~XAA5)1ODv1TJ976ZQR$JkhlwE}8 z(-O^*wZR=dISdhbyi9v5iPjutTb}&)Z0R9ojxHSD*qP`;4Yi=rgblUOj;5{$6H8e^ zEhd%74T{3s8`0u&Lpq9TH&)qU{q)O8%!M4U3>u>|;pb4%SAicR68ov&*5l@}@N+@i|%& z9a>?O(=l*Z#FUd9)kC?bMs|@Uy%b;lQg;&;qXN@jN!wH*F^1(<6^YbO1NliH;tf4L zMj5`bN1Mbti>fGPEvZy@d_!*vtI&V%2S3{}PQqUiM^wQFg7{i%&Zi{j%rMGu{uZ8r zC*dP}AXv`&V#Ya+@D(`6SBtIm<+*`^XooE@4keth+i+k{Uw&UDI8TI(&ivEw>?pkW-x(iCw+d7Xy&6jV8p6e2=S5dg0C07^l3h3~QIDHk^7GBPOjsueT zg%ITM^7}BMB|5=h!$9M&d=}lHE$cbFPlPORwjVM(il~(7N*%D`EFBO(n>Ee>CFPd! r6ThUge6N-I|9!1G{7L88SzT5;L*H4qd}lK}b(;=9OM_)`oErZJpndm_ literal 0 HcmV?d00001 diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java new file mode 100644 index 0000000..35e7197 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/DetectPeerCloseWithoutReadTest.java @@ -0,0 +1,212 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.FixedRecvByteBufAllocator; +import io.netty.channel.ServerChannel; +import io.netty.channel.SimpleChannelInboundHandler; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class DetectPeerCloseWithoutReadTest { + protected abstract EventLoopGroup newGroup(); + protected abstract Class serverChannel(); + protected abstract Class clientChannel(); + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void clientCloseWithoutServerReadIsDetectedNoExtraReadRequested() throws InterruptedException { + clientCloseWithoutServerReadIsDetected0(false); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void clientCloseWithoutServerReadIsDetectedExtraReadRequested() throws InterruptedException { + clientCloseWithoutServerReadIsDetected0(true); + } + + private void clientCloseWithoutServerReadIsDetected0(final boolean extraReadRequested) + throws InterruptedException { + EventLoopGroup serverGroup = null; + EventLoopGroup clientGroup = null; + Channel serverChannel = null; + try { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicInteger bytesRead = new AtomicInteger(); + final int expectedBytes = 100; + serverGroup = newGroup(); + clientGroup = newGroup(); + ServerBootstrap sb = new ServerBootstrap(); + sb.group(serverGroup); + sb.channel(serverChannel()); + // Ensure we read only one message per read() call and that we need multiple read() + // calls to consume everything. + sb.childOption(ChannelOption.AUTO_READ, false); + sb.childOption(ChannelOption.MAX_MESSAGES_PER_READ, 1); + sb.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(expectedBytes / 10)); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new TestHandler(bytesRead, extraReadRequested, latch)); + } + }); + + serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + + Bootstrap cb = new Bootstrap(); + cb.group(serverGroup); + cb.channel(clientChannel()); + cb.handler(new ChannelInboundHandlerAdapter()); + Channel clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + ByteBuf buf = clientChannel.alloc().buffer(expectedBytes); + buf.writerIndex(buf.writerIndex() + expectedBytes); + clientChannel.writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); + + latch.await(); + assertEquals(expectedBytes, bytesRead.get()); + } finally { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + if (serverGroup != null) { + serverGroup.shutdownGracefully(); + } + if (clientGroup != null) { + clientGroup.shutdownGracefully(); + } + } + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void serverCloseWithoutClientReadIsDetectedNoExtraReadRequested() throws InterruptedException { + serverCloseWithoutClientReadIsDetected0(false); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void serverCloseWithoutClientReadIsDetectedExtraReadRequested() throws InterruptedException { + serverCloseWithoutClientReadIsDetected0(true); + } + + private void serverCloseWithoutClientReadIsDetected0(final boolean extraReadRequested) throws InterruptedException { + EventLoopGroup serverGroup = null; + EventLoopGroup clientGroup = null; + Channel serverChannel = null; + Channel clientChannel = null; + try { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicInteger bytesRead = new AtomicInteger(); + final int expectedBytes = 100; + serverGroup = newGroup(); + clientGroup = newGroup(); + ServerBootstrap sb = new ServerBootstrap(); + sb.group(serverGroup); + sb.channel(serverChannel()); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) { + ByteBuf buf = ctx.alloc().buffer(expectedBytes); + buf.writerIndex(buf.writerIndex() + expectedBytes); + ctx.writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); + ctx.fireChannelActive(); + } + }); + } + }); + + serverChannel = sb.bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + + Bootstrap cb = new Bootstrap(); + cb.group(serverGroup); + cb.channel(clientChannel()); + // Ensure we read only one message per read() call and that we need multiple read() + // calls to consume everything. + cb.option(ChannelOption.AUTO_READ, false); + cb.option(ChannelOption.MAX_MESSAGES_PER_READ, 1); + cb.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(expectedBytes / 10)); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new TestHandler(bytesRead, extraReadRequested, latch)); + } + }); + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + + latch.await(); + assertEquals(expectedBytes, bytesRead.get()); + } finally { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + if (clientChannel != null) { + clientChannel.close().syncUninterruptibly(); + } + if (serverGroup != null) { + serverGroup.shutdownGracefully(); + } + if (clientGroup != null) { + clientGroup.shutdownGracefully(); + } + } + } + + private static final class TestHandler extends SimpleChannelInboundHandler { + private final AtomicInteger bytesRead; + private final boolean extraReadRequested; + private final CountDownLatch latch; + + TestHandler(AtomicInteger bytesRead, boolean extraReadRequested, CountDownLatch latch) { + this.bytesRead = bytesRead; + this.extraReadRequested = extraReadRequested; + this.latch = latch; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + bytesRead.addAndGet(msg.readableBytes()); + + if (extraReadRequested) { + // Because autoread is off, we call read to consume all data until we detect the close. + ctx.read(); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + latch.countDown(); + ctx.fireChannelInactive(); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java new file mode 100644 index 0000000..8745836 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollAbstractDomainSocketEchoTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.DomainSocketAddress; +import java.net.SocketAddress; +import java.util.UUID; + +public class EpollAbstractDomainSocketEchoTest extends EpollDomainSocketEchoTest { + @Override + protected SocketAddress newSocketAddress() { + // these don't actually show up in the file system so creating a temp file isn't reliable + return new DomainSocketAddress("\0" + System.getProperty("java.io.tmpdir") + UUID.randomUUID()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java new file mode 100644 index 0000000..8871978 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollChannelConfigTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.ChannelException; + +import io.netty.channel.unix.Buffer; +import io.netty.channel.unix.IntegerUnixChannelOption; +import io.netty.channel.unix.RawUnixChannelOption; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class EpollChannelConfigTest { + + @Test + public void testOptionGetThrowsChannelException() throws Exception { + Epoll.ensureAvailability(); + EpollSocketChannel channel = new EpollSocketChannel(); + channel.config().getSoLinger(); + channel.fd().close(); + try { + channel.config().getSoLinger(); + fail(); + } catch (ChannelException e) { + // expected + } + } + + @Test + public void testOptionSetThrowsChannelException() throws Exception { + Epoll.ensureAvailability(); + EpollSocketChannel channel = new EpollSocketChannel(); + channel.config().setKeepAlive(true); + channel.fd().close(); + try { + channel.config().setKeepAlive(true); + fail(); + } catch (ChannelException e) { + // expected + } + } + + @Test + public void testIntegerOption() throws Exception { + Epoll.ensureAvailability(); + EpollSocketChannel channel = new EpollSocketChannel(); + IntegerUnixChannelOption opt = new IntegerUnixChannelOption("INT_OPT", 1, 2); + Integer zero = 0; + assertEquals(zero, channel.config().getOption(opt)); + channel.config().setOption(opt, 1); + assertNotEquals(zero, channel.config().getOption(opt)); + channel.fd().close(); + } + + @Test + public void testRawOption() throws Exception { + Epoll.ensureAvailability(); + EpollSocketChannel channel = new EpollSocketChannel(); + // Value for SOL_SOCKET and SO_REUSEADDR + // See https://github.com/torvalds/linux/blob/v5.17/include/uapi/asm-generic/socket.h + RawUnixChannelOption opt = new RawUnixChannelOption("RAW_OPT", 1, 2, 4); + + ByteBuffer disabled = Buffer.allocateDirectWithNativeOrder(4); + disabled.putInt(0).flip(); + assertEquals(disabled, channel.config().getOption(opt)); + + ByteBuffer enabled = Buffer.allocateDirectWithNativeOrder(4); + enabled.putInt(1).flip(); + + channel.config().setOption(opt, enabled); + assertNotEquals(disabled, channel.config().getOption(opt)); + channel.fd().close(); + } +} + diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java new file mode 100644 index 0000000..17dd91e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollCompositeBufferGatheringWriteTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelConfig; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.CompositeBufferGatheringWriteTest; + +import java.util.List; + +public class EpollCompositeBufferGatheringWriteTest extends CompositeBufferGatheringWriteTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } + + @Override + protected void compositeBufferPartialWriteDoesNotCorruptDataInitServerConfig(ChannelConfig config, + int soSndBuf) { + if (config instanceof EpollChannelConfig) { + ((EpollChannelConfig) config).setMaxBytesPerGatheringWrite(soSndBuf); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java new file mode 100644 index 0000000..1a3beaa --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelConfigTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollDatagramChannelConfigTest { + + @Test + public void testIpFreeBind() throws Exception { + Epoll.ensureAvailability(); + EpollDatagramChannel channel = new EpollDatagramChannel(); + assertTrue(channel.config().setOption(EpollChannelOption.IP_FREEBIND, true)); + assertTrue(channel.config().getOption(EpollChannelOption.IP_FREEBIND)); + channel.fd().close(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java new file mode 100644 index 0000000..ab40ec5 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramChannelTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.unix.Socket; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import static io.netty.util.NetUtil.LOCALHOST; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollDatagramChannelTest { + + @BeforeEach + public void setUp() { + Epoll.ensureAvailability(); + } + + @Test + public void testDefaultMaxMessagePerRead() { + EpollDatagramChannel channel = new EpollDatagramChannel(); + assertEquals(16, channel.config().getMaxMessagesPerRead()); + channel.unsafe().closeForcibly(); + } + + @Test + public void testNotActiveNoLocalRemoteAddress() throws IOException { + checkNotActiveNoLocalRemoteAddress(new EpollDatagramChannel()); + checkNotActiveNoLocalRemoteAddress(new EpollDatagramChannel(InternetProtocolFamily.IPv4)); + checkNotActiveNoLocalRemoteAddress(new EpollDatagramChannel(InternetProtocolFamily.IPv6)); + } + + @Test + public void testActiveHasLocalAddress() throws IOException { + Socket socket = Socket.newSocketDgram(); + EpollDatagramChannel channel = new EpollDatagramChannel(socket.intValue()); + InetSocketAddress localAddress = channel.localAddress(); + assertTrue(channel.active); + assertNotNull(localAddress); + assertEquals(socket.localAddress(), localAddress); + channel.fd().close(); + } + + @Test + public void testLocalAddressBeforeAndAfterBind() { + EventLoopGroup group = new EpollEventLoopGroup(1); + try { + TestHandler handler = new TestHandler(); + InetSocketAddress localAddressBeforeBind = new InetSocketAddress(LOCALHOST, 0); + + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(EpollDatagramChannel.class) + .localAddress(localAddressBeforeBind) + .handler(handler); + + ChannelFuture future = bootstrap.bind().syncUninterruptibly(); + + assertNull(handler.localAddress); + + SocketAddress localAddressAfterBind = future.channel().localAddress(); + assertNotNull(localAddressAfterBind); + assertTrue(localAddressAfterBind instanceof InetSocketAddress); + assertTrue(((InetSocketAddress) localAddressAfterBind).getPort() != 0); + + future.channel().close().syncUninterruptibly(); + } finally { + group.shutdownGracefully(); + } + } + + private static void checkNotActiveNoLocalRemoteAddress(EpollDatagramChannel channel) throws IOException { + assertFalse(channel.active); + assertNull(channel.localAddress()); + assertNull(channel.remoteAddress()); + channel.fd().close(); + } + + private static final class TestHandler extends ChannelInboundHandlerAdapter { + private volatile SocketAddress localAddress; + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + this.localAddress = ctx.channel().localAddress(); + super.channelRegistered(ctx); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java new file mode 100644 index 0000000..23e1a3d --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramConnectNotExistsTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramConnectNotExistsTest; + +import java.util.List; + +public class EpollDatagramConnectNotExistsTest extends DatagramConnectNotExistsTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.datagramSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java new file mode 100644 index 0000000..6d7646c --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIPv6Test.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramMulticastIPv6Test; + +import java.util.List; + +public class EpollDatagramMulticastIPv6Test extends DatagramMulticastIPv6Test { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java new file mode 100644 index 0000000..ef77fa3 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastIpv6WithIpv4AddrTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.testsuite.transport.socket.DatagramMulticastTest; + +public class EpollDatagramMulticastIpv6WithIpv4AddrTest extends DatagramMulticastTest { + + @Override + protected InternetProtocolFamily groupInternetProtocalFamily() { + return InternetProtocolFamily.IPv4; + } + + @Override + protected InternetProtocolFamily socketInternetProtocalFamily() { + return InternetProtocolFamily.IPv6; + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java new file mode 100644 index 0000000..8c17853 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramMulticastTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramMulticastTest; + +import java.util.List; + +public class EpollDatagramMulticastTest extends DatagramMulticastTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java new file mode 100644 index 0000000..a9eaeb4 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramScatteringReadTest.java @@ -0,0 +1,307 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.AdaptiveRecvByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.AbstractDatagramTest; +import io.netty.util.internal.PlatformDependent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class EpollDatagramScatteringReadTest extends AbstractDatagramTest { + + @BeforeAll + public static void assumeRecvmmsgSupported() { + assumeTrue(Native.IS_SUPPORTING_RECVMMSG); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.epollOnlyDatagram(internetProtocolFamily()); + } + + @Test + public void testScatteringReadPartial(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testScatteringReadPartial(bootstrap, bootstrap2); + } + }); + } + + public void testScatteringReadPartial(Bootstrap sb, Bootstrap cb) throws Throwable { + testScatteringRead(sb, cb, false, true); + } + + @Test + public void testScatteringRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testScatteringRead(bootstrap, bootstrap2); + } + }); + } + + public void testScatteringRead(Bootstrap sb, Bootstrap cb) throws Throwable { + testScatteringRead(sb, cb, false, false); + } + + @Test + public void testScatteringReadConnectedPartial(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testScatteringReadConnectedPartial(bootstrap, bootstrap2); + } + }); + } + + public void testScatteringReadConnectedPartial(Bootstrap sb, Bootstrap cb) throws Throwable { + testScatteringRead(sb, cb, true, true); + } + + @Test + public void testScatteringConnectedRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testScatteringConnectedRead(bootstrap, bootstrap2); + } + }); + } + + public void testScatteringConnectedRead(Bootstrap sb, Bootstrap cb) throws Throwable { + testScatteringRead(sb, cb, true, false); + } + + private void testScatteringRead(Bootstrap sb, Bootstrap cb, boolean connected, boolean partial) throws Throwable { + int packetSize = 8; + int numPackets = 4; + + sb.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator( + packetSize, packetSize * (partial ? numPackets / 2 : numPackets), 64 * 1024)); + // Set the MAX_DATAGRAM_PAYLOAD_SIZE to something bigger then the actual packet size. + // This will allow us to check if we correctly thread the received len. + sb.option(EpollChannelOption.MAX_DATAGRAM_PAYLOAD_SIZE, packetSize * 2); + + Channel sc = null; + Channel cc = null; + + try { + cb.handler(new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msgs) throws Exception { + // Nothing will be sent. + } + }); + cc = cb.bind(newSocketAddress()).sync().channel(); + final SocketAddress ccAddress = cc.localAddress(); + + final AtomicReference errorRef = new AtomicReference(); + + final CountDownLatch latch = new CountDownLatch(numPackets); + sb.handler(new SimpleChannelInboundHandler() { + private long numRead; + private int counter; + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + assertTrue(counter > 1); + counter = 0; + ctx.read(); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) { + assertEquals(ccAddress, msg.sender()); + + // Each packet contains a long which represent the write iteration. + assertEquals(8, msg.content().readableBytes()); + assertEquals(numRead, msg.content().readLong()); + numRead++; + counter++; + latch.countDown(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errorRef.compareAndSet(null, cause); + } + }); + + sb.option(ChannelOption.AUTO_READ, false); + sc = sb.bind(newSocketAddress()).sync().channel(); + + if (connected) { + sc.connect(cc.localAddress()).syncUninterruptibly(); + } + + InetSocketAddress addr = (InetSocketAddress) sc.localAddress(); + + List futures = new ArrayList(numPackets); + for (int i = 0; i < numPackets; i++) { + futures.add(cc.write(new DatagramPacket(cc.alloc().directBuffer().writeLong(i), addr))); + } + + cc.flush(); + + for (ChannelFuture f: futures) { + f.sync(); + } + + // Enable autoread now which also triggers a read, this should cause scattering reads (recvmmsg) to happen. + sc.config().setAutoRead(true); + + if (!latch.await(10, TimeUnit.SECONDS)) { + Throwable error = errorRef.get(); + if (error != null) { + throw error; + } + fail("Timeout while waiting for packets"); + } + } finally { + if (cc != null) { + cc.close().syncUninterruptibly(); + } + if (sc != null) { + sc.close().syncUninterruptibly(); + } + } + } + + @Test + public void testScatteringReadWithSmallBuffer(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testScatteringReadWithSmallBuffer(bootstrap, bootstrap2); + } + }); + } + + public void testScatteringReadWithSmallBuffer(Bootstrap sb, Bootstrap cb) throws Throwable { + testScatteringReadWithSmallBuffer0(sb, cb, false); + } + + @Test + public void testScatteringConnectedReadWithSmallBuffer(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testScatteringConnectedReadWithSmallBuffer(bootstrap, bootstrap2); + } + }); + } + + public void testScatteringConnectedReadWithSmallBuffer(Bootstrap sb, Bootstrap cb) throws Throwable { + testScatteringReadWithSmallBuffer0(sb, cb, true); + } + + private void testScatteringReadWithSmallBuffer0(Bootstrap sb, Bootstrap cb, boolean connected) throws Throwable { + int packetSize = 16; + + sb.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(1400, 1400, 64 * 1024)); + sb.option(EpollChannelOption.MAX_DATAGRAM_PAYLOAD_SIZE, 1400); + + Channel sc = null; + Channel cc = null; + + try { + cb.handler(new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msgs) { + // Nothing will be sent. + } + }); + cc = cb.bind(newSocketAddress()).sync().channel(); + final SocketAddress ccAddress = cc.localAddress(); + + final AtomicReference errorRef = new AtomicReference(); + final byte[] bytes = new byte[packetSize]; + PlatformDependent.threadLocalRandom().nextBytes(bytes); + + final CountDownLatch latch = new CountDownLatch(1); + sb.handler(new SimpleChannelInboundHandler() { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) { + assertEquals(ccAddress, msg.sender()); + + assertEquals(bytes.length, msg.content().readableBytes()); + byte[] receivedBytes = new byte[bytes.length]; + msg.content().readBytes(receivedBytes); + assertArrayEquals(bytes, receivedBytes); + + latch.countDown(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errorRef.compareAndSet(null, cause); + } + }); + + sc = sb.bind(newSocketAddress()).sync().channel(); + + if (connected) { + sc.connect(cc.localAddress()).syncUninterruptibly(); + } + + InetSocketAddress addr = (InetSocketAddress) sc.localAddress(); + + cc.writeAndFlush(new DatagramPacket(cc.alloc().directBuffer().writeBytes(bytes), addr)).sync(); + + if (!latch.await(10, TimeUnit.SECONDS)) { + Throwable error = errorRef.get(); + if (error != null) { + throw error; + } + fail("Timeout while waiting for packets"); + } + } finally { + if (cc != null) { + cc.close().syncUninterruptibly(); + } + if (sc != null) { + sc.close().syncUninterruptibly(); + } + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java new file mode 100644 index 0000000..d50d4b1 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6MappedTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory; +import io.netty.testsuite.transport.socket.DatagramUnicastIPv6MappedTest; + +import java.util.List; + +public class EpollDatagramUnicastIPv6MappedTest extends DatagramUnicastIPv6MappedTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java new file mode 100644 index 0000000..aa7b6d0 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastIPv6Test.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramUnicastIPv6Test; + +import java.util.List; + +public class EpollDatagramUnicastIPv6Test extends DatagramUnicastIPv6Test { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.datagram(internetProtocolFamily()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java new file mode 100644 index 0000000..ba1970f --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDatagramUnicastTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.FixedRecvByteBufAllocator; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramUnicastInetTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class EpollDatagramUnicastTest extends DatagramUnicastInetTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.datagram(InternetProtocolFamily.IPv4); + } + + public void testSimpleSendWithConnect(Bootstrap sb, Bootstrap cb) throws Throwable { + // Run this test with IP_RECVORIGDSTADDR option enabled + sb.option(EpollChannelOption.IP_RECVORIGDSTADDR, true); + super.testSimpleSendWithConnect(sb, cb); + sb.option(EpollChannelOption.IP_RECVORIGDSTADDR, false); + } + + @Test + public void testSendSegmentedDatagramPacket(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSendSegmentedDatagramPacket(bootstrap, bootstrap2); + } + }); + } + + public void testSendSegmentedDatagramPacket(Bootstrap sb, Bootstrap cb) throws Throwable { + testSegmentedDatagramPacket(sb, cb, false, false); + } + + @Test + public void testSendSegmentedDatagramPacketComposite(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSendSegmentedDatagramPacketComposite(bootstrap, bootstrap2); + } + }); + } + + public void testSendSegmentedDatagramPacketComposite(Bootstrap sb, Bootstrap cb) throws Throwable { + testSegmentedDatagramPacket(sb, cb, true, false); + } + + @Test + public void testSendAndReceiveSegmentedDatagramPacket(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSendAndReceiveSegmentedDatagramPacket(bootstrap, bootstrap2); + } + }); + } + + public void testSendAndReceiveSegmentedDatagramPacket(Bootstrap sb, Bootstrap cb) throws Throwable { + testSegmentedDatagramPacket(sb, cb, false, true); + } + + @Test + public void testSendAndReceiveSegmentedDatagramPacketComposite(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSendAndReceiveSegmentedDatagramPacketComposite(bootstrap, bootstrap2); + } + }); + } + + public void testSendAndReceiveSegmentedDatagramPacketComposite(Bootstrap sb, Bootstrap cb) throws Throwable { + testSegmentedDatagramPacket(sb, cb, true, true); + } + + private void testSegmentedDatagramPacket(Bootstrap sb, Bootstrap cb, boolean composite, boolean gro) + throws Throwable { + if (!(cb.group() instanceof EpollEventLoopGroup)) { + // Only supported for the native epoll transport. + return; + } + if (gro && !(sb.group() instanceof EpollEventLoopGroup)) { + // Only supported for the native epoll transport. + return; + } + assumeTrue(EpollDatagramChannel.isSegmentedDatagramPacketSupported()); + Channel sc = null; + Channel cc = null; + + try { + cb.handler(new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msgs) { + // Nothing will be sent. + } + }); + + cc = cb.bind(newSocketAddress()).sync().channel(); + + final int numBuffers = 16; + final int segmentSize = 512; + int bufferCapacity = numBuffers * segmentSize; + final CountDownLatch latch = new CountDownLatch(numBuffers); + AtomicReference errorRef = new AtomicReference(); + if (gro) { + // Enable GRO and also ensure we can read everything with one read as otherwise + // we will drop things on the floor. + sb.option(EpollChannelOption.UDP_GRO, true); + sb.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(bufferCapacity)); + } + sc = sb.handler(new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) { + if (packet.content().readableBytes() == segmentSize) { + latch.countDown(); + } + } + }).bind(newSocketAddress()).sync().channel(); + + if (sc instanceof EpollDatagramChannel) { + assertEquals(gro, sc.config().getOption(EpollChannelOption.UDP_GRO)); + } + InetSocketAddress addr = sendToAddress((InetSocketAddress) sc.localAddress()); + final ByteBuf buffer; + if (composite) { + CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer(); + for (int i = 0; i < numBuffers; i++) { + compositeBuffer.addComponent(true, + Unpooled.directBuffer(segmentSize).writeZero(segmentSize)); + } + buffer = compositeBuffer; + } else { + buffer = Unpooled.directBuffer(bufferCapacity).writeZero(bufferCapacity); + } + cc.writeAndFlush(new io.netty.channel.unix.SegmentedDatagramPacket(buffer, segmentSize, addr)).sync(); + + if (!latch.await(10, TimeUnit.SECONDS)) { + Throwable error = errorRef.get(); + if (error != null) { + throw error; + } + fail(); + } + } finally { + if (cc != null) { + cc.close().sync(); + } + if (sc != null) { + sc.close().sync(); + } + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java new file mode 100644 index 0000000..22a5492 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDetectPeerCloseWithoutReadTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; + +public class EpollDetectPeerCloseWithoutReadTest extends DetectPeerCloseWithoutReadTest { + @Override + protected EventLoopGroup newGroup() { + return new EpollEventLoopGroup(2); + } + + @Override + protected Class serverChannel() { + return EpollServerSocketChannel.class; + } + + @Override + protected Class clientChannel() { + return EpollSocketChannel.class; + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java new file mode 100644 index 0000000..60eb72c --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramChannelTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EpollDomainDatagramChannelTest { + + @BeforeEach + public void setUp() { + Epoll.ensureAvailability(); + } + + @Test + public void testDefaultMaxMessagePerRead() { + EpollDomainDatagramChannel channel = new EpollDomainDatagramChannel(); + assertEquals(16, channel.config().getMaxMessagesPerRead()); + channel.unsafe().closeForcibly(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java new file mode 100644 index 0000000..be2f902 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramPathTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.unix.DomainDatagramPacket; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.AbstractClientSocketTest; +import io.netty.util.CharsetUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.io.FileNotFoundException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class EpollDomainDatagramPathTest extends AbstractClientSocketTest { + + @Test + void testConnectPathDoesNotExist(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) { + try { + bootstrap.handler(new ChannelInboundHandlerAdapter()) + .connect(EpollSocketTestPermutation.newDomainSocketAddress()).sync().channel(); + fail("Expected FileNotFoundException"); + } catch (Exception e) { + assertTrue(e instanceof FileNotFoundException); + } + } + }); + } + + @Test + void testWriteReceiverPathDoesNotExist(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) { + try { + Channel ch = bootstrap.handler(new ChannelInboundHandlerAdapter()) + .bind(EpollSocketTestPermutation.newDomainSocketAddress()).sync().channel(); + ch.writeAndFlush(new DomainDatagramPacket( + Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII), + EpollSocketTestPermutation.newDomainSocketAddress())).sync(); + fail("Expected FileNotFoundException"); + } catch (Exception e) { + assertTrue(e instanceof FileNotFoundException); + } + } + }); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainDatagramSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java new file mode 100644 index 0000000..371488d --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainDatagramUnicastTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2021 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.unix.DomainDatagramChannel; +import io.netty.channel.unix.DomainDatagramPacket; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.DatagramUnicastTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.net.SocketAddress; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class EpollDomainDatagramUnicastTest extends DatagramUnicastTest { + + @Test + void testBind(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testBind(bootstrap2); + } + }); + } + + private void testBind(Bootstrap cb) throws Throwable { + Channel channel = null; + try { + channel = cb.handler(new ChannelInboundHandlerAdapter()) + .bind(newSocketAddress()).sync().channel(); + assertThat(channel.localAddress()).isNotNull() + .isInstanceOf(DomainSocketAddress.class); + } finally { + closeChannel(channel); + } + } + + @Override + protected boolean supportDisconnect() { + return false; + } + + @Override + protected boolean isConnected(Channel channel) { + return ((DomainDatagramChannel) channel).isConnected(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainDatagram(); + } + + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected Channel setupClientChannel(Bootstrap cb, final byte[] bytes, final CountDownLatch latch, + final AtomicReference errorRef) throws Throwable { + cb.handler(new SimpleChannelInboundHandler() { + + @Override + public void channelRead0(ChannelHandlerContext ctx, DomainDatagramPacket msg) { + try { + ByteBuf buf = msg.content(); + assertEquals(bytes.length, buf.readableBytes()); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i)); + } + + assertEquals(ctx.channel().localAddress(), msg.recipient()); + } finally { + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errorRef.compareAndSet(null, cause); + } + }); + return cb.bind(newSocketAddress()).sync().channel(); + } + + @Override + protected Channel setupServerChannel(Bootstrap sb, final byte[] bytes, final SocketAddress sender, + final CountDownLatch latch, final AtomicReference errorRef, + final boolean echo) throws Throwable { + sb.handler(new SimpleChannelInboundHandler() { + + @Override + public void channelRead0(ChannelHandlerContext ctx, DomainDatagramPacket msg) { + try { + if (sender == null) { + assertNotNull(msg.sender()); + } else { + assertEquals(sender, msg.sender()); + } + + ByteBuf buf = msg.content(); + assertEquals(bytes.length, buf.readableBytes()); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i)); + } + + assertEquals(ctx.channel().localAddress(), msg.recipient()); + + if (echo) { + ctx.writeAndFlush(new DomainDatagramPacket(buf.retainedDuplicate(), msg.sender())); + } + } finally { + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errorRef.compareAndSet(null, cause); + } + }); + return sb.bind(newSocketAddress()).sync().channel(); + } + + @Override + protected ChannelFuture write(Channel cc, ByteBuf buf, SocketAddress remote, WrapType wrapType) { + switch (wrapType) { + case DUP: + return cc.write(new DomainDatagramPacket(buf.retainedDuplicate(), (DomainSocketAddress) remote)); + case SLICE: + return cc.write(new DomainDatagramPacket(buf.retainedSlice(), (DomainSocketAddress) remote)); + case READ_ONLY: + return cc.write(new DomainDatagramPacket(buf.retain().asReadOnly(), (DomainSocketAddress) remote)); + case NONE: + return cc.write(new DomainDatagramPacket(buf.retain(), (DomainSocketAddress) remote)); + default: + throw new Error("unknown wrap type: " + wrapType); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java new file mode 100644 index 0000000..5433eb8 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketAddressesTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketAddressesTest; + +import java.net.SocketAddress; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class EpollDomainSocketAddressesTest extends SocketAddressesTest { + + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } + + @Override + protected void assertAddress(SocketAddress address) { + assertNotNull(((DomainSocketAddress) address).path()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java new file mode 100644 index 0000000..49b90e4 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketDataReadInitialStateTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketDataReadInitialStateTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketDataReadInitialStateTest extends SocketDataReadInitialStateTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java new file mode 100644 index 0000000..3e8c642 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketEchoTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketEchoTest extends EpollSocketEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java new file mode 100644 index 0000000..6013908 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFdTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.unix.DomainSocketReadMode; +import io.netty.channel.unix.FileDescriptor; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.AbstractSocketTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.net.SocketAddress; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollDomainSocketFdTest extends AbstractSocketTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSendRecvFd(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSendRecvFd(serverBootstrap, bootstrap); + } + }); + } + + public void testSendRecvFd(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final BlockingQueue queue = new LinkedBlockingQueue(1); + sb.childHandler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + // Create new channel and obtain a file descriptor from it. + final EpollDomainSocketChannel ch = new EpollDomainSocketChannel(); + + ctx.writeAndFlush(ch.fd()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + Throwable cause = future.cause(); + queue.offer(cause); + } + } + }); + } + }); + cb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + FileDescriptor fd = (FileDescriptor) msg; + queue.offer(fd); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + queue.add(cause); + ctx.close(); + } + }); + cb.option(EpollChannelOption.DOMAIN_SOCKET_READ_MODE, + DomainSocketReadMode.FILE_DESCRIPTORS); + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + Object received = queue.take(); + cc.close().sync(); + sc.close().sync(); + + if (received instanceof FileDescriptor) { + FileDescriptor fd = (FileDescriptor) received; + assertTrue(fd.isOpen()); + fd.close(); + assertFalse(fd.isOpen()); + assertNull(queue.poll()); + } else { + throw (Throwable) received; + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java new file mode 100644 index 0000000..b77f50c --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFileRegionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketFileRegionTest extends EpollSocketFileRegionTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java new file mode 100644 index 0000000..1c34328 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketFixedLengthEchoTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketFixedLengthEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketFixedLengthEchoTest extends SocketFixedLengthEchoTest { + + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java new file mode 100644 index 0000000..5e25a93 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketGatheringWriteTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketGatheringWriteTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketGatheringWriteTest extends SocketGatheringWriteTest { + + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java new file mode 100644 index 0000000..fc7d7ce --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketObjectEchoTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketObjectEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketObjectEchoTest extends SocketObjectEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java new file mode 100644 index 0000000..8a18feb --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketReuseFdTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.AbstractSocketReuseFdTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketReuseFdTest extends AbstractSocketReuseFdTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java new file mode 100644 index 0000000..d60e2c5 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketShutdownOutputByPeerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +package io.netty.channel.epoll; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.unix.Buffer; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; +import io.netty.testsuite.transport.socket.AbstractSocketShutdownOutputByPeerTest; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.List; + +public class EpollDomainSocketShutdownOutputByPeerTest extends AbstractSocketShutdownOutputByPeerTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.serverDomainSocket(); + } + + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected void shutdownOutput(LinuxSocket s) throws IOException { + s.shutdown(false, true); + } + + @Override + protected void connect(LinuxSocket s, SocketAddress address) throws IOException { + s.connect(address); + } + + @Override + protected void close(LinuxSocket s) throws IOException { + s.close(); + } + + @Override + protected void write(LinuxSocket s, int data) throws IOException { + final ByteBuffer buf = Buffer.allocateDirectWithNativeOrder(4); + buf.putInt(data); + buf.flip(); + s.send(buf, buf.position(), buf.limit()); + Buffer.free(buf); + } + + @Override + protected LinuxSocket newSocket() { + return LinuxSocket.newSocketDomain(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java new file mode 100644 index 0000000..64858ce --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslClientRenegotiateTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslClientRenegotiateTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketSslClientRenegotiateTest extends SocketSslClientRenegotiateTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } + + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java new file mode 100644 index 0000000..87755ab --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslEchoTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketSslEchoTest extends SocketSslEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java new file mode 100644 index 0000000..9ff9f15 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketSslGreetingTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslGreetingTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketSslGreetingTest extends SocketSslGreetingTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java new file mode 100644 index 0000000..dfdf6ed --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStartTlsTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStartTlsTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketStartTlsTest extends SocketStartTlsTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java new file mode 100644 index 0000000..29945ed --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollDomainSocketStringEchoTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStringEchoTest; + +import java.net.SocketAddress; +import java.util.List; + +public class EpollDomainSocketStringEchoTest extends SocketStringEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return EpollSocketTestPermutation.newDomainSocketAddress(); + } + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.domainSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java new file mode 100644 index 0000000..330aa57 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketAutoReadTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketAutoReadTest; + +import java.util.List; + +public class EpollETSocketAutoReadTest extends SocketAutoReadTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java new file mode 100644 index 0000000..ffff608 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketConditionalWritabilityTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketConditionalWritabilityTest; + +import java.util.List; + +public class EpollETSocketConditionalWritabilityTest extends SocketConditionalWritabilityTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java new file mode 100644 index 0000000..b246234 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketDataReadInitialStateTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketDataReadInitialStateTest; + +import java.util.List; + +public class EpollETSocketDataReadInitialStateTest extends SocketDataReadInitialStateTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java new file mode 100644 index 0000000..06beed2 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketExceptionHandlingTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketExceptionHandlingTest; + +import java.util.List; + +public class EpollETSocketExceptionHandlingTest extends SocketExceptionHandlingTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java new file mode 100644 index 0000000..336f0e4 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketHalfClosedTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketHalfClosedTest; + +import java.util.List; + +public class EpollETSocketHalfClosedTest extends SocketHalfClosedTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java new file mode 100644 index 0000000..7fe3ebc --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketReadPendingTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketReadPendingTest; + +import java.util.List; + +public class EpollETSocketReadPendingTest extends SocketReadPendingTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java new file mode 100644 index 0000000..916cd21 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollETSocketStringEchoBusyWaitTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; + +public class EpollETSocketStringEchoBusyWaitTest extends EpollSocketStringEchoBusyWaitTest { + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java new file mode 100644 index 0000000..f08149b --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollEventLoopTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.testsuite.transport.AbstractSingleThreadEventLoopTest; +import io.netty.channel.DefaultSelectStrategyFactory; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.channel.unix.FileDescriptor; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.RejectedExecutionHandlers; +import io.netty.util.concurrent.ThreadPerTaskExecutor; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollEventLoopTest extends AbstractSingleThreadEventLoopTest { + + @Override + protected boolean supportsChannelIteration() { + return true; + } + + @Override + protected EventLoopGroup newEventLoopGroup() { + return new EpollEventLoopGroup(); + } + + @Override + protected ServerSocketChannel newChannel() { + return new EpollServerSocketChannel(); + } + + @Override + protected Class serverChannelClass() { + return EpollServerSocketChannel.class; + } + + @Test + public void testScheduleBigDelayNotOverflow() { + final AtomicReference capture = new AtomicReference(); + + final EventLoopGroup group = new EpollEventLoop(null, + new ThreadPerTaskExecutor(new DefaultThreadFactory(getClass())), 0, + DefaultSelectStrategyFactory.INSTANCE.newSelectStrategy(), RejectedExecutionHandlers.reject(), + null, null) { + @Override + void handleLoopException(Throwable t) { + capture.set(t); + super.handleLoopException(t); + } + }; + + try { + final EventLoop eventLoop = group.next(); + Future future = eventLoop.schedule(new Runnable() { + @Override + public void run() { + // NOOP + } + }, Long.MAX_VALUE, TimeUnit.MILLISECONDS); + + assertFalse(future.awaitUninterruptibly(1000)); + assertTrue(future.cancel(true)); + assertNull(capture.get()); + } finally { + group.shutdownGracefully(); + } + } + + @Test + public void testEventFDETSemantics() throws Throwable { + final FileDescriptor epoll = Native.newEpollCreate(); + final FileDescriptor eventFd = Native.newEventFd(); + final FileDescriptor timerFd = Native.newTimerFd(); + final EpollEventArray array = new EpollEventArray(1024); + try { + Native.epollCtlAdd(epoll.intValue(), eventFd.intValue(), Native.EPOLLIN | Native.EPOLLET); + final AtomicReference causeRef = new AtomicReference(); + final AtomicInteger integer = new AtomicInteger(); + final Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + for (int i = 0; i < 2; i++) { + int ready = Native.epollWait(epoll, array, timerFd, -1, -1); + assertEquals(1, ready); + assertEquals(eventFd.intValue(), array.fd(0)); + integer.incrementAndGet(); + } + } catch (IOException e) { + causeRef.set(e); + } + } + }); + t.start(); + Native.eventFdWrite(eventFd.intValue(), 1); + + // Spin until we was the wakeup. + while (integer.get() != 1) { + Thread.sleep(10); + } + // Sleep for a short moment to ensure there is not other wakeup. + Thread.sleep(1000); + assertEquals(1, integer.get()); + Native.eventFdWrite(eventFd.intValue(), 1); + t.join(); + Throwable cause = causeRef.get(); + if (cause != null) { + throw cause; + } + assertEquals(2, integer.get()); + } finally { + array.free(); + epoll.close(); + eventFd.close(); + timerFd.close(); + } + } + + @Test + public void testResultNoTimeoutCorrectlyEncoded() throws Throwable { + final FileDescriptor epoll = Native.newEpollCreate(); + final FileDescriptor eventFd = Native.newEventFd(); + final FileDescriptor timerFd = Native.newTimerFd(); + final EpollEventArray array = new EpollEventArray(1024); + try { + Native.epollCtlAdd(epoll.intValue(), eventFd.intValue(), Native.EPOLLIN | Native.EPOLLET); + final AtomicReference causeRef = new AtomicReference(); + final Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + for (;;) { + long ready = Native.epollWait(epoll, array, timerFd, 0, 0, 10); + if (ready > 0) { + assertEquals(1, Native.epollReady(ready)); + assertEquals(eventFd.intValue(), array.fd(0)); + return; + } + Thread.sleep(100); + } + } catch (IOException e) { + causeRef.set(e); + } catch (InterruptedException ignore) { + // ignore + } + } + }); + t.start(); + Native.eventFdWrite(eventFd.intValue(), 1); + + t.join(); + Throwable cause = causeRef.get(); + if (cause != null) { + throw cause; + } + } finally { + array.free(); + epoll.close(); + eventFd.close(); + timerFd.close(); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java new file mode 100644 index 0000000..61f1e8c --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollJdkLoopbackSocketSslEchoTest.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import java.net.SocketAddress; + +public class EpollJdkLoopbackSocketSslEchoTest extends EpollSocketSslEchoTest { + @Override + protected SocketAddress newSocketAddress() { + return UnixTestUtils.newInetLoopbackSocketAddress(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java new file mode 100644 index 0000000..8379b41 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollKQueueIovArrayTest.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import org.junit.jupiter.api.BeforeAll; + +public class EpollKQueueIovArrayTest extends IovArrayTest { + + @BeforeAll + public static void loadNative() { + Epoll.ensureAvailability(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java new file mode 100644 index 0000000..8f6b794 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketAutoReadTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketAutoReadTest; + +import java.util.List; + +public class EpollLTSocketAutoReadTest extends SocketAutoReadTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java new file mode 100644 index 0000000..9bfb258 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketConditionalWritabilityTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketConditionalWritabilityTest; + +import java.util.List; + +public class EpollLTSocketConditionalWritabilityTest extends SocketConditionalWritabilityTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java new file mode 100644 index 0000000..5d48439 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketDataReadInitialStateTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketDataReadInitialStateTest; + +import java.util.List; + +public class EpollLTSocketDataReadInitialStateTest extends SocketDataReadInitialStateTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java new file mode 100644 index 0000000..f11635e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketExceptionHandlingTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketExceptionHandlingTest; + +import java.util.List; + +public class EpollLTSocketExceptionHandlingTest extends SocketExceptionHandlingTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java new file mode 100644 index 0000000..154d7e7 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketHalfClosedTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketHalfClosedTest; + +import java.util.List; + +public class EpollLTSocketHalfClosedTest extends SocketHalfClosedTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java new file mode 100644 index 0000000..46b8b1b --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketReadPendingTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketReadPendingTest; + +import java.util.List; + +public class EpollLTSocketReadPendingTest extends SocketReadPendingTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java new file mode 100644 index 0000000..21aaf16 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollLTSocketStringEchoBusyWaitTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; + +public class EpollLTSocketStringEchoBusyWaitTest extends EpollSocketStringEchoBusyWaitTest { + + @Override + protected void configure(ServerBootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + super.configure(bootstrap, bootstrap2, allocator); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED) + .childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap2.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java new file mode 100644 index 0000000..046bfd5 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollReuseAddrTest.java @@ -0,0 +1,263 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.util.NetUtil; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.ResourceLeakDetector; +import io.netty.util.internal.logging.InternalLogLevel; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class EpollReuseAddrTest { + private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(EpollReuseAddrTest.class); + + private static final int MAJOR; + private static final int MINOR; + private static final int BUGFIX; + static { + String kernelVersion = Native.KERNEL_VERSION; + int index = kernelVersion.indexOf('-'); + if (index > -1) { + kernelVersion = kernelVersion.substring(0, index); + } + String[] versionParts = kernelVersion.split("\\."); + if (versionParts.length <= 3) { + MAJOR = Integer.parseInt(versionParts[0]); + MINOR = Integer.parseInt(versionParts[1]); + if (versionParts.length == 3) { + int bugFix; + try { + bugFix = Integer.parseInt(versionParts[2]); + } catch (NumberFormatException ignore) { + // the last part of the version may include all kind of different things. Especially when + // someone compiles his / her own kernel. + // Just ignore a parse error here and use 0. + bugFix = 0; + } + BUGFIX = bugFix; + } else { + BUGFIX = 0; + } + } else { + LOGGER.log(InternalLogLevel.INFO, "Unable to parse kernel version: " + kernelVersion); + MAJOR = 0; + MINOR = 0; + BUGFIX = 0; + } + } + + @Test + public void testMultipleBindSocketChannelWithoutReusePortFails() { + assumeTrue(versionEqOrGt(3, 9, 0)); + testMultipleBindDatagramChannelWithoutReusePortFails0(createServerBootstrap()); + } + + @Test + public void testMultipleBindDatagramChannelWithoutReusePortFails() { + assumeTrue(versionEqOrGt(3, 9, 0)); + testMultipleBindDatagramChannelWithoutReusePortFails0(createBootstrap()); + } + + private static void testMultipleBindDatagramChannelWithoutReusePortFails0(AbstractBootstrap bootstrap) { + bootstrap.handler(new LoggingHandler(LogLevel.ERROR)); + ChannelFuture future = bootstrap.bind().syncUninterruptibly(); + try { + bootstrap.bind(future.channel().localAddress()).syncUninterruptibly(); + fail(); + } catch (Exception e) { + assertTrue(e instanceof IOException); + } + future.channel().close().syncUninterruptibly(); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testMultipleBindSocketChannel() throws Exception { + assumeTrue(versionEqOrGt(3, 9, 0)); + ServerBootstrap bootstrap = createServerBootstrap(); + bootstrap.option(EpollChannelOption.SO_REUSEPORT, true); + final AtomicBoolean accepted1 = new AtomicBoolean(); + bootstrap.childHandler(new ServerSocketTestHandler(accepted1)); + ChannelFuture future = bootstrap.bind().syncUninterruptibly(); + InetSocketAddress address1 = (InetSocketAddress) future.channel().localAddress(); + + final AtomicBoolean accepted2 = new AtomicBoolean(); + bootstrap.childHandler(new ServerSocketTestHandler(accepted2)); + ChannelFuture future2 = bootstrap.bind(address1).syncUninterruptibly(); + InetSocketAddress address2 = (InetSocketAddress) future2.channel().localAddress(); + + assertEquals(address1, address2); + while (!accepted1.get() || !accepted2.get()) { + Socket socket = new Socket(address1.getAddress(), address1.getPort()); + socket.setReuseAddress(true); + socket.close(); + } + future.channel().close().syncUninterruptibly(); + future2.channel().close().syncUninterruptibly(); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + @Disabled // TODO: Unignore after making it pass on centos6-1 and debian7-1 + public void testMultipleBindDatagramChannel() throws Exception { + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED); + assumeTrue(versionEqOrGt(3, 9, 0)); + Bootstrap bootstrap = createBootstrap(); + bootstrap.option(EpollChannelOption.SO_REUSEPORT, true); + final AtomicBoolean received1 = new AtomicBoolean(); + bootstrap.handler(new DatagramSocketTestHandler(received1)); + ChannelFuture future = bootstrap.bind().syncUninterruptibly(); + final InetSocketAddress address1 = (InetSocketAddress) future.channel().localAddress(); + + final AtomicBoolean received2 = new AtomicBoolean(); + bootstrap.handler(new DatagramSocketTestHandler(received2)); + ChannelFuture future2 = bootstrap.bind(address1).syncUninterruptibly(); + final InetSocketAddress address2 = (InetSocketAddress) future2.channel().localAddress(); + + assertEquals(address1, address2); + final byte[] bytes = "data".getBytes(); + + // fire up 16 Threads and send DatagramPackets to make sure we stress it enough to see DatagramPackets received + // on both sockets. + int count = 16; + final CountDownLatch latch = new CountDownLatch(count); + Runnable r = new Runnable() { + @Override + public void run() { + try { + DatagramSocket socket = new DatagramSocket(); + while (!received1.get() || !received2.get()) { + socket.send(new DatagramPacket( + bytes, 0, bytes.length, address1.getAddress(), address1.getPort())); + } + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + latch.countDown(); + } + }; + + ExecutorService executor = Executors.newFixedThreadPool(count); + for (int i = 0 ; i < count; i++) { + executor.execute(r); + } + latch.await(); + executor.shutdown(); + future.channel().close().syncUninterruptibly(); + future2.channel().close().syncUninterruptibly(); + assertTrue(received1.get()); + assertTrue(received2.get()); + } + + private static ServerBootstrap createServerBootstrap() { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(EpollSocketTestPermutation.EPOLL_BOSS_GROUP, EpollSocketTestPermutation.EPOLL_WORKER_GROUP); + bootstrap.channel(EpollServerSocketChannel.class); + bootstrap.childHandler(new DummyHandler()); + InetSocketAddress address = new InetSocketAddress(NetUtil.LOCALHOST, 0); + bootstrap.localAddress(address); + return bootstrap; + } + + private static Bootstrap createBootstrap() { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(EpollSocketTestPermutation.EPOLL_WORKER_GROUP); + bootstrap.channel(EpollDatagramChannel.class); + InetSocketAddress address = new InetSocketAddress(NetUtil.LOCALHOST, 0); + bootstrap.localAddress(address); + return bootstrap; + } + + private static boolean versionEqOrGt(int major, int minor, int bugfix) { + if (MAJOR > major) { + return true; + } + if (MAJOR == major) { + if (MINOR > minor) { + return true; + } else if (MINOR == minor) { + if (BUGFIX >= bugfix) { + return true; + } + } + } + return false; + } + + @ChannelHandler.Sharable + private static class ServerSocketTestHandler extends ChannelInboundHandlerAdapter { + private final AtomicBoolean accepted; + + ServerSocketTestHandler(AtomicBoolean accepted) { + this.accepted = accepted; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + accepted.set(true); + ctx.close(); + } + } + + @ChannelHandler.Sharable + private static class DatagramSocketTestHandler extends ChannelInboundHandlerAdapter { + private final AtomicBoolean received; + + DatagramSocketTestHandler(AtomicBoolean received) { + this.received = received; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ReferenceCountUtil.release(msg); + received.set(true); + } + } + + @ChannelHandler.Sharable + private static final class DummyHandler extends ChannelHandlerAdapter { } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java new file mode 100644 index 0000000..013fff0 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollServerSocketChannelConfigTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.net.InetSocketAddress; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollServerSocketChannelConfigTest { + + private static EventLoopGroup group; + private static EpollServerSocketChannel ch; + + @BeforeAll + public static void before() { + group = new EpollEventLoopGroup(1); + ServerBootstrap bootstrap = new ServerBootstrap(); + ch = (EpollServerSocketChannel) bootstrap.group(group) + .channel(EpollServerSocketChannel.class) + .childHandler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + } + + @AfterAll + public static void after() { + try { + ch.close().syncUninterruptibly(); + } finally { + group.shutdownGracefully(); + } + } + + @Test + public void testTcpDeferAccept() { + ch.config().setTcpDeferAccept(0); + assertEquals(0, ch.config().getTcpDeferAccept()); + ch.config().setTcpDeferAccept(10); + // The returned value may be bigger then what we set. + // See https://www.spinics.net/lists/netdev/msg117330.html + assertTrue(10 <= ch.config().getTcpDeferAccept()); + } + + @Test + public void testReusePort() { + ch.config().setReusePort(false); + assertFalse(ch.config().isReusePort()); + ch.config().setReusePort(true); + assertTrue(ch.config().isReusePort()); + } + + @Test + public void testFreeBind() { + ch.config().setFreeBind(false); + assertFalse(ch.config().isFreeBind()); + ch.config().setFreeBind(true); + assertTrue(ch.config().isFreeBind()); + } + + @Test + public void getGetOptions() { + Map, Object> map = ch.config().getOptions(); + assertFalse(map.isEmpty()); + } + + @Test + public void testFastOpen() { + assertThrows(IllegalArgumentException.class, new Executable() { + @Override + public void execute() { + ch.config().setTcpFastopen(-1); + } + }); + ch.config().setTcpFastopen(10); + assertEquals(10, ch.config().getTcpFastopen()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java new file mode 100644 index 0000000..b51a00c --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketAddressesTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketAddressesTest; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EpollSocketAddressesTest extends SocketAddressesTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } + + @Override + protected void assertAddress(SocketAddress address) { + assertTrue(((InetSocketAddress) address).getPort() > 0); + assertFalse(((InetSocketAddress) address).getAddress().isAnyLocalAddress()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java new file mode 100644 index 0000000..4e4e9fd --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelConfigTest.java @@ -0,0 +1,178 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.opentest4j.TestAbortedException; + +import java.net.InetSocketAddress; +import java.nio.channels.ClosedChannelException; +import java.util.Map; +import java.util.Random; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class EpollSocketChannelConfigTest { + + private static EventLoopGroup group; + private static EpollSocketChannel ch; + private static Random rand; + + @BeforeAll + public static void beforeClass() { + rand = new Random(); + group = new EpollEventLoopGroup(1); + } + + @AfterAll + public static void afterClass() { + group.shutdownGracefully(); + } + + @BeforeEach + public void setup() { + Bootstrap bootstrap = new Bootstrap(); + ch = (EpollSocketChannel) bootstrap.group(group) + .channel(EpollSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + } + + @AfterEach + public void teardown() { + ch.close().syncUninterruptibly(); + } + + private static long randLong(long min, long max) { + return min + nextLong(max - min + 1); + } + + private static long nextLong(long n) { + long bits, val; + do { + bits = (rand.nextLong() << 1) >>> 1; + val = bits % n; + } while (bits - val + (n - 1) < 0L); + return val; + } + + @Test + public void testRandomTcpNotSentLowAt() { + final long expected = randLong(0, 0xFFFFFFFFL); + final long actual; + try { + ch.config().setTcpNotSentLowAt(expected); + actual = ch.config().getTcpNotSentLowAt(); + } catch (RuntimeException e) { + throw new TestAbortedException("assumeNoException", e); + } + assertEquals(expected, actual); + } + + @Test + public void testInvalidHighTcpNotSentLowAt() { + try { + final long value = 0xFFFFFFFFL + 1; + ch.config().setTcpNotSentLowAt(value); + } catch (IllegalArgumentException e) { + return; + } catch (RuntimeException e) { + throw new TestAbortedException("assumeNoException", e); + } + fail(); + } + + @Test + public void testInvalidLowTcpNotSentLowAt() { + try { + final long value = -1; + ch.config().setTcpNotSentLowAt(value); + } catch (IllegalArgumentException e) { + return; + } catch (RuntimeException e) { + throw new TestAbortedException("assumeNoException", e); + } + fail(); + } + + @Test + public void testTcpCork() { + ch.config().setTcpCork(false); + assertFalse(ch.config().isTcpCork()); + ch.config().setTcpCork(true); + assertTrue(ch.config().isTcpCork()); + } + + @Test + public void testTcpQickAck() { + ch.config().setTcpQuickAck(false); + assertFalse(ch.config().isTcpQuickAck()); + ch.config().setTcpQuickAck(true); + assertTrue(ch.config().isTcpQuickAck()); + } + + // For this test to pass, we are relying on the sockets file descriptor not being reused after the socket is closed. + // This is inherently racy, so we allow getSoLinger to throw ChannelException a few of times, but eventually we do + // want to see a ClosedChannelException for the test to pass. + @RepeatedIfExceptionsTest(repeats = 4) + public void testSetOptionWhenClosed() { + ch.close().syncUninterruptibly(); + ChannelException e = assertThrows(ChannelException.class, new Executable() { + @Override + public void execute() throws Throwable { + ch.config().setSoLinger(0); + } + }); + assertThat(e).hasCauseInstanceOf(ClosedChannelException.class); + } + + // For this test to pass, we are relying on the sockets file descriptor not being reused after the socket is closed. + // This is inherently racy, so we allow getSoLinger to throw ChannelException a few of times, but eventually we do + // want to see a ClosedChannelException for the test to pass. + @RepeatedIfExceptionsTest(repeats = 4) + public void testGetOptionWhenClosed() { + ch.close().syncUninterruptibly(); + ChannelException e = assertThrows(ChannelException.class, new Executable() { + @Override + public void execute() throws Throwable { + ch.config().getSoLinger(); + } + }); + assertThat(e).hasCauseInstanceOf(ClosedChannelException.class); + } + + @Test + public void getGetOptions() { + Map, Object> map = ch.config().getOptions(); + assertFalse(map.isEmpty()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java new file mode 100644 index 0000000..5cbfaf0 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelNotYetConnectedTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketChannelNotYetConnectedTest; + +import java.util.List; + +public class EpollSocketChannelNotYetConnectedTest extends SocketChannelNotYetConnectedTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.clientSocketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java new file mode 100644 index 0000000..689ad07 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketChannelTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; + +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollSocketChannelTest { + + @Test + public void testTcpInfo() throws Exception { + EventLoopGroup group = new EpollEventLoopGroup(1); + + try { + Bootstrap bootstrap = new Bootstrap(); + EpollSocketChannel ch = (EpollSocketChannel) bootstrap.group(group) + .channel(EpollSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + EpollTcpInfo info = ch.tcpInfo(); + assertTcpInfo0(info); + ch.close().syncUninterruptibly(); + } finally { + group.shutdownGracefully(); + } + } + + @Test + public void testTcpInfoReuse() throws Exception { + EventLoopGroup group = new EpollEventLoopGroup(1); + + try { + Bootstrap bootstrap = new Bootstrap(); + EpollSocketChannel ch = (EpollSocketChannel) bootstrap.group(group) + .channel(EpollSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + EpollTcpInfo info = new EpollTcpInfo(); + ch.tcpInfo(info); + assertTcpInfo0(info); + ch.close().syncUninterruptibly(); + } finally { + group.shutdownGracefully(); + } + } + + private static void assertTcpInfo0(EpollTcpInfo info) throws Exception { + assertNotNull(info); + + assertTrue(info.state() >= 0); + assertTrue(info.caState() >= 0); + assertTrue(info.retransmits() >= 0); + assertTrue(info.probes() >= 0); + assertTrue(info.backoff() >= 0); + assertTrue(info.options() >= 0); + assertTrue(info.sndWscale() >= 0); + assertTrue(info.rcvWscale() >= 0); + assertTrue(info.rto() >= 0); + assertTrue(info.ato() >= 0); + assertTrue(info.sndMss() >= 0); + assertTrue(info.rcvMss() >= 0); + assertTrue(info.unacked() >= 0); + assertTrue(info.sacked() >= 0); + assertTrue(info.lost() >= 0); + assertTrue(info.retrans() >= 0); + assertTrue(info.fackets() >= 0); + assertTrue(info.lastDataSent() >= 0); + assertTrue(info.lastAckSent() >= 0); + assertTrue(info.lastDataRecv() >= 0); + assertTrue(info.lastAckRecv() >= 0); + assertTrue(info.pmtu() >= 0); + assertTrue(info.rcvSsthresh() >= 0); + assertTrue(info.rtt() >= 0); + assertTrue(info.rttvar() >= 0); + assertTrue(info.sndSsthresh() >= 0); + assertTrue(info.sndCwnd() >= 0); + assertTrue(info.advmss() >= 0); + assertTrue(info.reordering() >= 0); + assertTrue(info.rcvRtt() >= 0); + assertTrue(info.rcvSpace() >= 0); + assertTrue(info.totalRetrans() >= 0); + } + + // See https://github.com/netty/netty/issues/7159 + @Test + public void testSoLingerNoAssertError() throws Exception { + EventLoopGroup group = new EpollEventLoopGroup(1); + + try { + Bootstrap bootstrap = new Bootstrap(); + EpollSocketChannel ch = (EpollSocketChannel) bootstrap.group(group) + .channel(EpollSocketChannel.class) + .option(ChannelOption.SO_LINGER, 10) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + ch.close().syncUninterruptibly(); + } finally { + group.shutdownGracefully(); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java new file mode 100644 index 0000000..43b561e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketCloseForciblyTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketCloseForciblyTest; + +import java.util.List; + +public class EpollSocketCloseForciblyTest extends SocketCloseForciblyTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java new file mode 100644 index 0000000..378f41e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketConnectTest; + +import java.util.List; + +public class EpollSocketConnectTest extends SocketConnectTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java new file mode 100644 index 0000000..da636fc --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketConnectionAttemptTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketConnectionAttemptTest; + +import java.util.List; + +public class EpollSocketConnectionAttemptTest extends SocketConnectionAttemptTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.clientSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java new file mode 100644 index 0000000..61e89fa --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketEchoTest; + +import java.util.List; + +public class EpollSocketEchoTest extends SocketEchoTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java new file mode 100644 index 0000000..a28ae00 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFileRegionTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketFileRegionTest; + +import java.util.List; + +public class EpollSocketFileRegionTest extends SocketFileRegionTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java new file mode 100644 index 0000000..92cb620 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketFixedLengthEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketFixedLengthEchoTest; + +import java.util.List; + +public class EpollSocketFixedLengthEchoTest extends SocketFixedLengthEchoTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java new file mode 100644 index 0000000..7ae8a5a --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketGatheringWriteTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketGatheringWriteTest; + +import java.util.List; + +public class EpollSocketGatheringWriteTest extends SocketGatheringWriteTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java new file mode 100644 index 0000000..b703135 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketMultipleConnectTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketMultipleConnectTest; + +import java.util.ArrayList; +import java.util.List; + +public class EpollSocketMultipleConnectTest extends SocketMultipleConnectTest { + + @Override + protected List> newFactories() { + List> factories + = new ArrayList>(); + for (TestsuitePermutation.BootstrapComboFactory comboFactory + : EpollSocketTestPermutation.INSTANCE.socketWithFastOpen()) { + EventLoopGroup group = comboFactory.newClientInstance().config().group(); + if (group instanceof NioEventLoopGroup || group instanceof EpollEventLoopGroup) { + factories.add(comboFactory); + } + } + return factories; + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java new file mode 100644 index 0000000..78e7527 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketObjectEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketObjectEchoTest; + +import java.util.List; + +public class EpollSocketObjectEchoTest extends SocketObjectEchoTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java new file mode 100644 index 0000000..3fa8b44 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketRstTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.unix.Errors; +import io.netty.channel.unix.Errors.NativeIoException; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketRstTest; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollSocketRstTest extends SocketRstTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithoutFastOpen(); + } + + @Override + protected void assertRstOnCloseException(IOException cause, Channel clientChannel) { + if (!AbstractEpollChannel.class.isInstance(clientChannel)) { + super.assertRstOnCloseException(cause, clientChannel); + return; + } + + assertTrue(cause instanceof NativeIoException, + "actual [type, message]: [" + cause.getClass() + ", " + cause.getMessage() + "]"); + assertEquals(Errors.ERRNO_ECONNRESET_NEGATIVE, ((NativeIoException) cause).expectedErr()); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java new file mode 100644 index 0000000..69ae170 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputByPeerTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketShutdownOutputByPeerTest; + +import java.util.List; + +public class EpollSocketShutdownOutputByPeerTest extends SocketShutdownOutputByPeerTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.serverSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java new file mode 100644 index 0000000..0fdc285 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketShutdownOutputBySelfTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketShutdownOutputBySelfTest; + +import java.util.List; + +public class EpollSocketShutdownOutputBySelfTest extends SocketShutdownOutputBySelfTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.clientSocket(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java new file mode 100644 index 0000000..d73357b --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslClientRenegotiateTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslClientRenegotiateTest; + +import java.util.List; + +public class EpollSocketSslClientRenegotiateTest extends SocketSslClientRenegotiateTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java new file mode 100644 index 0000000..e2502ac --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslEchoTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslEchoTest; + +import java.util.List; + +public class EpollSocketSslEchoTest extends SocketSslEchoTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java new file mode 100644 index 0000000..065268a --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslGreetingTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslGreetingTest; + +import java.util.List; + +public class EpollSocketSslGreetingTest extends SocketSslGreetingTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java new file mode 100644 index 0000000..e3a2828 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketSslSessionReuseTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketSslSessionReuseTest; + +import java.util.List; + +public class EpollSocketSslSessionReuseTest extends SocketSslSessionReuseTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java new file mode 100644 index 0000000..132338b --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStartTlsTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStartTlsTest; + +import java.util.List; + +public class EpollSocketStartTlsTest extends SocketStartTlsTest { + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java new file mode 100644 index 0000000..954ca4b --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoBusyWaitTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SelectStrategy; +import io.netty.channel.SelectStrategyFactory; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; +import io.netty.testsuite.transport.socket.SocketStringEchoTest; +import io.netty.util.IntSupplier; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +import java.util.ArrayList; +import java.util.List; + +public class EpollSocketStringEchoBusyWaitTest extends SocketStringEchoTest { + + private static EventLoopGroup EPOLL_LOOP; + + @BeforeAll + public static void setup() throws Exception { + EPOLL_LOOP = new EpollEventLoopGroup(2, new DefaultThreadFactory("testsuite-epoll-busy-wait", true), + new SelectStrategyFactory() { + @Override + public SelectStrategy newSelectStrategy() { + return new SelectStrategy() { + @Override + public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) { + return SelectStrategy.BUSY_WAIT; + } + }; + } + }); + } + + @AfterAll + public static void teardown() throws Exception { + if (EPOLL_LOOP != null) { + EPOLL_LOOP.shutdownGracefully(); + } + } + + @Override + protected List> newFactories() { + List> list = + new ArrayList>(); + final BootstrapFactory sbf = serverSocket(); + final BootstrapFactory cbf = clientSocket(); + list.add(new BootstrapComboFactory() { + @Override + public ServerBootstrap newServerInstance() { + return sbf.newInstance(); + } + + @Override + public Bootstrap newClientInstance() { + return cbf.newInstance(); + } + }); + + return list; + } + + private static BootstrapFactory serverSocket() { + return new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(EPOLL_LOOP, EPOLL_LOOP).channel(EpollServerSocketChannel.class); + } + }; + } + + private static BootstrapFactory clientSocket() { + return new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_LOOP).channel(EpollSocketChannel.class); + } + }; + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java new file mode 100644 index 0000000..b25a40a --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketStringEchoTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.SocketStringEchoTest; + +import java.util.List; + +public class EpollSocketStringEchoTest extends SocketStringEchoTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.socketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java new file mode 100644 index 0000000..6bbcb2e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTcpMd5Test.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.ConnectTimeoutException; +import io.netty.channel.EventLoopGroup; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class EpollSocketTcpMd5Test { + private static final byte[] SERVER_KEY = "abc".getBytes(CharsetUtil.US_ASCII); + private static final byte[] BAD_KEY = "def".getBytes(CharsetUtil.US_ASCII); + private static EventLoopGroup GROUP; + private EpollServerSocketChannel server; + + @BeforeAll + public static void beforeClass() { + GROUP = new EpollEventLoopGroup(1); + } + + @AfterAll + public static void afterClass() { + GROUP.shutdownGracefully(); + } + + @BeforeEach + public void setup() { + Bootstrap bootstrap = new Bootstrap(); + server = (EpollServerSocketChannel) bootstrap.group(GROUP) + .channel(EpollServerSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(NetUtil.LOCALHOST4, 0)).syncUninterruptibly().channel(); + } + + @AfterEach + public void teardown() { + server.close().syncUninterruptibly(); + } + + @Test + public void testServerSocketChannelOption() throws Exception { + server.config().setOption(EpollChannelOption.TCP_MD5SIG, + Collections.singletonMap(NetUtil.LOCALHOST4, SERVER_KEY)); + server.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.emptyMap()); + } + + @Test + public void testServerOption() throws Exception { + Bootstrap bootstrap = new Bootstrap(); + EpollServerSocketChannel ch = (EpollServerSocketChannel) bootstrap.group(GROUP) + .channel(EpollServerSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .bind(new InetSocketAddress(0)).syncUninterruptibly().channel(); + + ch.config().setOption(EpollChannelOption.TCP_MD5SIG, + Collections.singletonMap(NetUtil.LOCALHOST4, SERVER_KEY)); + ch.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.emptyMap()); + + ch.close().syncUninterruptibly(); + } + + @Test + public void testKeyMismatch() throws Exception { + server.config().setOption(EpollChannelOption.TCP_MD5SIG, + Collections.singletonMap(NetUtil.LOCALHOST4, SERVER_KEY)); + + assertThrows(ConnectTimeoutException.class, new Executable() { + @Override + public void execute() throws Throwable { + EpollSocketChannel client = (EpollSocketChannel) new Bootstrap().group(GROUP) + .channel(EpollSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .option(EpollChannelOption.TCP_MD5SIG, + Collections.singletonMap(NetUtil.LOCALHOST4, BAD_KEY)) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) + .connect(server.localAddress()).syncUninterruptibly().channel(); + client.close().syncUninterruptibly(); + } + }); + } + + @Test + public void testKeyMatch() throws Exception { + server.config().setOption(EpollChannelOption.TCP_MD5SIG, + Collections.singletonMap(NetUtil.LOCALHOST4, SERVER_KEY)); + + EpollSocketChannel client = (EpollSocketChannel) new Bootstrap().group(GROUP) + .channel(EpollSocketChannel.class) + .handler(new ChannelInboundHandlerAdapter()) + .option(EpollChannelOption.TCP_MD5SIG, + Collections.singletonMap(NetUtil.LOCALHOST4, SERVER_KEY)) + .connect(server.localAddress()).syncUninterruptibly().channel(); + client.close().syncUninterruptibly(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTest.java new file mode 100644 index 0000000..a5238b0 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.PeerCredentials; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollSocketTest extends SocketTest { + @BeforeAll + public static void loadJNI() { + Epoll.ensureAvailability(); + } + + @Test + public void testTcpCork() throws Exception { + assertFalse(socket.isTcpCork()); + socket.setTcpCork(true); + assertTrue(socket.isTcpCork()); + } + + @Test + public void testPeerCreds() throws IOException { + LinuxSocket s1 = LinuxSocket.newSocketDomain(); + LinuxSocket s2 = LinuxSocket.newSocketDomain(); + + try { + DomainSocketAddress dsa = UnixTestUtils.newDomainSocketAddress(); + s1.bind(dsa); + s1.listen(1); + + assertTrue(s2.connect(dsa)); + byte [] addr = new byte[64]; + s1.accept(addr); + PeerCredentials pc = s1.getPeerCredentials(); + assertNotEquals(pc.uid(), -1); + } finally { + s1.close(); + s2.close(); + } + } + + @Override + protected LinuxSocket newSocket() { + return LinuxSocket.newSocketStream(); + } + + @Override + protected int level() { + // Value for SOL_SOCKET + // See https://github.com/torvalds/linux/blob/v5.17/include/uapi/asm-generic/socket.h + return 1; + } + + @Override + protected int optname() { + // Value for SO_REUSEADDR + // See https://github.com/torvalds/linux/blob/v5.17/include/uapi/asm-generic/socket.h + return 2; + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java new file mode 100644 index 0000000..36be0ed --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSocketTestPermutation.java @@ -0,0 +1,264 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; +import io.netty.testsuite.transport.socket.SocketTestPermutation; +import io.netty.util.concurrent.DefaultThreadFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static io.netty.channel.epoll.Native.IS_SUPPORTING_TCP_FASTOPEN_CLIENT; +import static io.netty.channel.epoll.Native.IS_SUPPORTING_TCP_FASTOPEN_SERVER; + +class EpollSocketTestPermutation extends SocketTestPermutation { + + static final EpollSocketTestPermutation INSTANCE = new EpollSocketTestPermutation(); + + static final EventLoopGroup EPOLL_BOSS_GROUP = + new EpollEventLoopGroup(BOSSES, new DefaultThreadFactory("testsuite-epoll-boss", true)); + static final EventLoopGroup EPOLL_WORKER_GROUP = + new EpollEventLoopGroup(WORKERS, new DefaultThreadFactory("testsuite-epoll-worker", true)); + + @Override + public List> socket() { + List> list = + combo(serverSocket(), clientSocketWithFastOpen()); + + list.remove(list.size() - 1); // Exclude NIO x NIO test + + return list; + } + + public List> socketWithoutFastOpen() { + List> list = + combo(serverSocket(), clientSocket()); + + list.remove(list.size() - 1); // Exclude NIO x NIO test + + return list; + } + + @Override + public List> serverSocket() { + List> toReturn = new ArrayList>(); + toReturn.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(EPOLL_BOSS_GROUP, EPOLL_WORKER_GROUP) + .channel(EpollServerSocketChannel.class); + } + }); + if (IS_SUPPORTING_TCP_FASTOPEN_SERVER) { + toReturn.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + ServerBootstrap serverBootstrap = new ServerBootstrap().group(EPOLL_BOSS_GROUP, EPOLL_WORKER_GROUP) + .channel(EpollServerSocketChannel.class); + serverBootstrap.option(ChannelOption.TCP_FASTOPEN, 5); + return serverBootstrap; + } + }); + } + toReturn.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(nioBossGroup, nioWorkerGroup) + .channel(NioServerSocketChannel.class); + } + }); + + return toReturn; + } + + @Override + public List> clientSocket() { + List> toReturn = new ArrayList>(); + + toReturn.add(new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channel(EpollSocketChannel.class); + } + }); + + toReturn.add(new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channel(NioSocketChannel.class); + } + }); + + return toReturn; + } + + @Override + public List> clientSocketWithFastOpen() { + List> factories = clientSocket(); + + if (IS_SUPPORTING_TCP_FASTOPEN_CLIENT) { + int insertIndex = factories.size() - 1; // Keep NIO fixture last. + factories.add(insertIndex, new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channel(EpollSocketChannel.class) + .option(ChannelOption.TCP_FASTOPEN_CONNECT, true); + } + }); + } + + return factories; + } + + @Override + public List> datagram( + final InternetProtocolFamily family) { + // Make the list of Bootstrap factories. + List> bfs = Arrays.asList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new NioDatagramChannel(family); + } + + @Override + public String toString() { + return NioDatagramChannel.class.getSimpleName() + ".class"; + } + }); + } + }, + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new EpollDatagramChannel(family); + } + + @Override + public String toString() { + return InternetProtocolFamily.class.getSimpleName() + ".class"; + } + }); + } + } + ); + return combo(bfs, bfs); + } + + List> epollOnlyDatagram( + final InternetProtocolFamily family) { + return combo(Collections.singletonList(datagramBootstrapFactory(family)), + Collections.singletonList(datagramBootstrapFactory(family))); + } + + private static BootstrapFactory datagramBootstrapFactory(final InternetProtocolFamily family) { + return new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new EpollDatagramChannel(family); + } + + @Override + public String toString() { + return InternetProtocolFamily.class.getSimpleName() + ".class"; + } + }); + } + }; + } + + public List> domainSocket() { + return combo(serverDomainSocket(), clientDomainSocket()); + } + + public List> serverDomainSocket() { + return Collections.>singletonList( + new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(EPOLL_BOSS_GROUP, EPOLL_WORKER_GROUP) + .channel(EpollServerDomainSocketChannel.class); + } + } + ); + } + + public List> clientDomainSocket() { + return Collections.>singletonList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channel(EpollDomainSocketChannel.class); + } + } + ); + } + + @Override + public List> datagramSocket() { + return Collections.>singletonList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channel(EpollDatagramChannel.class); + } + } + ); + } + + public List> domainDatagram() { + return combo(domainDatagramSocket(), domainDatagramSocket()); + } + + public List> domainDatagramSocket() { + return Collections.>singletonList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(EPOLL_WORKER_GROUP).channel(EpollDomainDatagramChannel.class); + } + } + ); + } + + public static DomainSocketAddress newDomainSocketAddress() { + return UnixTestUtils.newDomainSocketAddress(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java new file mode 100644 index 0000000..53d6c58 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java @@ -0,0 +1,313 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.unix.FileDescriptor; +import io.netty.util.NetUtil; +import io.netty.util.internal.PlatformDependent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EpollSpliceTest { + + private static final int SPLICE_LEN = 32 * 1024; + private static final Random random = new Random(); + private static final byte[] data = new byte[1048576]; + + static { + random.nextBytes(data); + } + + @Test + public void spliceToSocket() throws Throwable { + final EchoHandler sh = new EchoHandler(); + final EchoHandler ch = new EchoHandler(); + + EventLoopGroup group = new EpollEventLoopGroup(1); + ServerBootstrap bs = new ServerBootstrap(); + bs.channel(EpollServerSocketChannel.class); + bs.group(group).childHandler(sh); + final Channel sc = bs.bind(NetUtil.LOCALHOST, 0).syncUninterruptibly().channel(); + + ServerBootstrap bs2 = new ServerBootstrap(); + bs2.channel(EpollServerSocketChannel.class); + bs2.childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bs2.group(group).childHandler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(final ChannelHandlerContext ctx) throws Exception { + ctx.channel().config().setAutoRead(false); + Bootstrap bs = new Bootstrap(); + bs.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + + bs.channel(EpollSocketChannel.class); + bs.group(ctx.channel().eventLoop()).handler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext context) throws Exception { + final EpollSocketChannel ch = (EpollSocketChannel) ctx.channel(); + final EpollSocketChannel ch2 = (EpollSocketChannel) context.channel(); + // We are splicing two channels together, at this point we have a tcp proxy which handles all + // the data transfer only in kernel space! + + // Integer.MAX_VALUE will splice infinitely. + ch.spliceTo(ch2, Integer.MAX_VALUE).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + future.channel().close(); + } + } + }); + // Trigger multiple splices to see if partial splicing works as well. + ch2.spliceTo(ch, SPLICE_LEN).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + future.channel().close(); + } else { + ch2.spliceTo(ch, SPLICE_LEN).addListener(this); + } + } + }); + ctx.channel().config().setAutoRead(true); + } + + @Override + public void channelInactive(ChannelHandlerContext context) throws Exception { + context.close(); + } + }); + bs.connect(sc.localAddress()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + ctx.close(); + } else { + future.channel().closeFuture().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + ctx.close(); + } + }); + } + } + }); + } + }); + Channel pc = bs2.bind(NetUtil.LOCALHOST, 0).syncUninterruptibly().channel(); + + Bootstrap cb = new Bootstrap(); + cb.group(group); + cb.channel(EpollSocketChannel.class); + cb.handler(ch); + Channel cc = cb.connect(pc.localAddress()).syncUninterruptibly().channel(); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + ByteBuf buf = Unpooled.wrappedBuffer(data, i, length); + cc.writeAndFlush(buf); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + pc.close().sync(); + group.shutdownGracefully(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void spliceToFile() throws Throwable { + EventLoopGroup group = new EpollEventLoopGroup(1); + File file = PlatformDependent.createTempFile("netty-splice", null, null); + file.deleteOnExit(); + + SpliceHandler sh = new SpliceHandler(file); + ServerBootstrap bs = new ServerBootstrap(); + bs.channel(EpollServerSocketChannel.class); + bs.group(group).childHandler(sh); + bs.childOption(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + Channel sc = bs.bind(NetUtil.LOCALHOST, 0).syncUninterruptibly().channel(); + + Bootstrap cb = new Bootstrap(); + cb.group(group); + cb.channel(EpollSocketChannel.class); + cb.handler(new ChannelInboundHandlerAdapter()); + Channel cc = cb.connect(sc.localAddress()).syncUninterruptibly().channel(); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + ByteBuf buf = Unpooled.wrappedBuffer(data, i, length); + cc.writeAndFlush(buf); + i += length; + } + + while (sh.future2 == null || !sh.future2.isDone() || !sh.future.isDone()) { + if (sh.exception.get() != null) { + break; + } + Thread.sleep(50); + } + + sc.close().sync(); + cc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + + byte[] written = new byte[data.length]; + FileInputStream in = new FileInputStream(file); + + try { + assertEquals(written.length, in.read(written)); + assertArrayEquals(data, written); + } finally { + in.close(); + group.shutdownGracefully(); + } + } + + private static class EchoHandler extends SimpleChannelInboundHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.parent() != null) { + channel.write(Unpooled.wrappedBuffer(actual)); + } + + counter += actual.length; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + cause.printStackTrace(); + ctx.close(); + } + } + } + + private static class SpliceHandler extends ChannelInboundHandlerAdapter { + private final File file; + + volatile ChannelFuture future; + volatile ChannelFuture future2; + final AtomicReference exception = new AtomicReference(); + + SpliceHandler(File file) { + this.file = file; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + final EpollSocketChannel ch = (EpollSocketChannel) ctx.channel(); + final FileDescriptor fd = FileDescriptor.from(file); + + // splice two halves separately to test starting offset + future = ch.spliceTo(fd, 0, data.length / 2); + future2 = ch.spliceTo(fd, data.length / 2, data.length / 2); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + cause.printStackTrace(); + ctx.close(); + } + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollTest.java new file mode 100644 index 0000000..3e09949 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.FileDescriptor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EpollTest { + + @Test + public void testIsAvailable() { + assertTrue(Epoll.isAvailable()); + } + + // Testcase for https://github.com/netty/netty/issues/8444 + @Test + @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) + public void testEpollWaitWithTimeOutMinusOne() throws Exception { + final EpollEventArray eventArray = new EpollEventArray(8); + try { + final FileDescriptor epoll = Native.newEpollCreate(); + final FileDescriptor timerFd = Native.newTimerFd(); + final FileDescriptor eventfd = Native.newEventFd(); + Native.epollCtlAdd(epoll.intValue(), timerFd.intValue(), Native.EPOLLIN); + Native.epollCtlAdd(epoll.intValue(), eventfd.intValue(), Native.EPOLLIN); + + final AtomicReference ref = new AtomicReference(); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + assertEquals(1, Native.epollWait(epoll, eventArray, false)); + // This should have been woken up because of eventfd_write. + assertEquals(eventfd.intValue(), eventArray.fd(0)); + } catch (Throwable cause) { + ref.set(cause); + } + } + }); + t.start(); + t.join(1000); + assertTrue(t.isAlive()); + Native.eventFdWrite(eventfd.intValue(), 1); + + t.join(); + assertNull(ref.get()); + epoll.close(); + timerFd.close(); + eventfd.close(); + } finally { + eventArray.free(); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java new file mode 100644 index 0000000..7e93faf --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/EpollWriteBeforeRegisteredTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.bootstrap.Bootstrap; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.testsuite.transport.socket.WriteBeforeRegisteredTest; + +import java.util.List; + +public class EpollWriteBeforeRegisteredTest extends WriteBeforeRegisteredTest { + + @Override + protected List> newFactories() { + return EpollSocketTestPermutation.INSTANCE.clientSocketWithFastOpen(); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/IovArrayTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/IovArrayTest.java new file mode 100644 index 0000000..0660c0e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/IovArrayTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.buffer.UnpooledDirectByteBuf; +import io.netty.channel.unix.IovArray; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public abstract class IovArrayTest { + + @Test + public void testNotFailsWihtoutMemoryAddress() { + ByteBuf buffer = new NoMemoryAddressByteBuf(128); + IovArray array = new IovArray(buffer); + + ByteBuf buf = Unpooled.directBuffer().writeZero(8); + ByteBuf buf2 = new NoMemoryAddressByteBuf(8).writeZero(8); + assertTrue(array.add(buf, 0, buf.readableBytes())); + assertTrue(array.add(buf, 0, buf2.readableBytes())); + assertEquals(2, array.count()); + assertEquals(16, array.size()); + assertTrue(buf.release()); + assertTrue(buf2.release()); + assertNotEquals(-1, array.memoryAddress(0)); + array.release(); + assertEquals(0, buffer.refCnt()); + } + + private static final class NoMemoryAddressByteBuf extends UnpooledDirectByteBuf { + + NoMemoryAddressByteBuf(int capacity) { + super(UnpooledByteBufAllocator.DEFAULT, capacity, Integer.MAX_VALUE); + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java new file mode 100644 index 0000000..4fe962e --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/LinuxSocketTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.channel.unix.Errors.NativeIoException; +import io.netty.channel.unix.Socket; +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class LinuxSocketTest { + @BeforeAll + public static void loadJNI() { + Epoll.ensureAvailability(); + } + + @Test + public void testBindNonIpv6SocketToInet6AddressThrows() throws Exception { + final LinuxSocket socket = LinuxSocket.newSocketStream(false); + try { + assertThrows(IOException.class, new Executable() { + @Override + public void execute() throws Throwable { + socket.bind(new InetSocketAddress(InetAddress.getByAddress( + new byte[]{'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'}), + 0)); + } + }); + } finally { + socket.close(); + } + } + + @Test + public void testConnectNonIpv6SocketToInet6AddressThrows() throws Exception { + final LinuxSocket socket = LinuxSocket.newSocketStream(false); + try { + assertThrows(IOException.class, + new Executable() { + @Override + public void execute() throws Throwable { + socket.connect(new InetSocketAddress(InetAddress.getByAddress(new byte[]{ + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1'}), + 1234)); + } + }); + } finally { + socket.close(); + } + } + + @Test + public void testUnixDomainSocketTooLongPathFails() throws IOException { + // Most systems has a limit for UDS path of 108, 255 is generally too long. + StringBuilder socketPath = new StringBuilder("/tmp/"); + while (socketPath.length() < 255) { + socketPath.append(UUID.randomUUID()); + } + + final DomainSocketAddress domainSocketAddress = new DomainSocketAddress( + socketPath.toString()); + final Socket socket = Socket.newSocketDomain(); + try { + Exception exception = Assertions.assertThrows(NativeIoException.class, new Executable() { + @Override + public void execute() throws Throwable { + socket.bind(domainSocketAddress); + } + }); + Assertions.assertTrue(exception.getMessage().contains("too long")); + } finally { + socket.close(); + } + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/NativeTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/NativeTest.java new file mode 100644 index 0000000..09e04e9 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/NativeTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.util.NetUtil; +import org.junit.jupiter.api.Test; + +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +import static io.netty.channel.unix.NativeInetAddress.address; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NativeTest { + + @Test + public void testAddressIpv4() throws Exception { + InetSocketAddress inetAddress = new InetSocketAddress(NetUtil.LOCALHOST4, 9999); + byte[] bytes = new byte[8]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.put(inetAddress.getAddress().getAddress()); + buffer.putInt(inetAddress.getPort()); + assertEquals(inetAddress, address(buffer.array(), 0, bytes.length)); + } + + @Test + public void testAddressIpv6() throws Exception { + Inet6Address address = NetUtil.LOCALHOST6; + InetSocketAddress inetAddress = new InetSocketAddress(address, 9999); + byte[] bytes = new byte[24]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.put(address.getAddress()); + buffer.putInt(address.getScopeId()); + buffer.putInt(inetAddress.getPort()); + assertEquals(inetAddress, address(buffer.array(), 0, bytes.length)); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/SocketTest.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/SocketTest.java new file mode 100644 index 0000000..ca522d7 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/SocketTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.Buffer; +import io.netty.channel.unix.Socket; +import java.io.IOException; +import java.nio.ByteBuffer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public abstract class SocketTest { + protected T socket; + + protected abstract T newSocket(); + + @BeforeEach + public void setup() { + socket = newSocket(); + } + + @AfterEach + public void tearDown() throws IOException { + socket.close(); + } + + @Test + public void testKeepAlive() throws Exception { + assertFalse(socket.isKeepAlive()); + socket.setKeepAlive(true); + assertTrue(socket.isKeepAlive()); + } + + @Test + public void testTcpNoDelay() throws Exception { + assertFalse(socket.isTcpNoDelay()); + socket.setTcpNoDelay(true); + assertTrue(socket.isTcpNoDelay()); + } + + @Test + public void testReceivedBufferSize() throws Exception { + int size = socket.getReceiveBufferSize(); + int newSize = 65535; + assertTrue(size > 0); + socket.setReceiveBufferSize(newSize); + // Linux usually set it to double what is specified + assertTrue(newSize <= socket.getReceiveBufferSize()); + } + + @Test + public void testSendBufferSize() throws Exception { + int size = socket.getSendBufferSize(); + int newSize = 65535; + assertTrue(size > 0); + socket.setSendBufferSize(newSize); + // Linux usually set it to double what is specified + assertTrue(newSize <= socket.getSendBufferSize()); + } + + @Test + public void testSoLinger() throws Exception { + assertEquals(-1, socket.getSoLinger()); + socket.setSoLinger(10); + assertEquals(10, socket.getSoLinger()); + } + + @Test + public void testDoubleCloseDoesNotThrow() throws IOException { + Socket socket = Socket.newSocketStream(); + socket.close(); + socket.close(); + } + + @Test + public void testTrafficClass() throws IOException { + // IPTOS_THROUGHPUT + final int value = 0x08; + socket.setTrafficClass(value); + assertEquals(value, socket.getTrafficClass()); + } + + @Test + public void testIntOpt() throws IOException { + socket.setReuseAddress(false); + socket.setIntOpt(level(), optname(), 1); + // Anything which is != 0 is considered enabled + assertNotEquals(0, socket.getIntOpt(level(), optname())); + socket.setIntOpt(level(), optname(), 0); + // This should be disabled again + assertEquals(0, socket.getIntOpt(level(), optname())); + } + + @Test + public void testRawOpt() throws IOException { + ByteBuffer buffer = Buffer.allocateDirectWithNativeOrder(4); + buffer.putInt(1).flip(); + socket.setRawOpt(level(), optname(), buffer); + + ByteBuffer out = ByteBuffer.allocate(4); + socket.getRawOpt(level(), optname(), out); + assertFalse(out.hasRemaining()); + + out.flip(); + assertNotEquals(ByteBuffer.allocate(0), out); + } + + protected int level() { + throw new TestAbortedException("Not supported"); + } + + protected int optname() { + throw new TestAbortedException("Not supported"); + } +} diff --git a/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/UnixTestUtils.java b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/UnixTestUtils.java new file mode 100644 index 0000000..264ac16 --- /dev/null +++ b/netty-channel-epoll-native/src/test/java/io/netty/channel/epoll/UnixTestUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.epoll; + +import io.netty.channel.unix.DomainSocketAddress; +import io.netty.util.internal.PlatformDependent; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public final class UnixTestUtils { + private static final Object INET_LOOPBACK_UNAVAILABLE = new Object(); + private static volatile Object inetLoopbackCache; + + /** + * @deprecated Use {@link #newDomainSocketAddress()} instead. + */ + @Deprecated + public static DomainSocketAddress newSocketAddress() { + return newDomainSocketAddress(); + } + + public static DomainSocketAddress newDomainSocketAddress() { + try { + File file; + do { + file = PlatformDependent.createTempFile("NETTY", "UDS", null); + if (!file.delete()) { + throw new IOException("failed to delete: " + file); + } + } while (file.getAbsolutePath().length() > 128); + return new DomainSocketAddress(file); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * The JDK method may produce IPv4 loopback addresses where {@link io.netty.util.NetUtil#LOCALHOST} might be an + * IPv6 addresses. + * This difference can stress the system in different ways that are important to test. + */ + public static SocketAddress newInetLoopbackSocketAddress() { + Object loopback = inetLoopbackCache; + + if (loopback == null) { + inetLoopbackCache = loopback = getLoopbackAddress(); + } + + assumeTrue(loopback != INET_LOOPBACK_UNAVAILABLE, "InetAddress.getLoopbackAddress() is not available"); + return new InetSocketAddress((InetAddress) loopback, 0); + } + + private static Object getLoopbackAddress() { + try { + Method method = InetAddress.class.getMethod("getLoopbackAddress"); + return method.invoke(null); + } catch (Exception ignore) { + return INET_LOOPBACK_UNAVAILABLE; + } + } + + private UnixTestUtils() { } +} diff --git a/netty-channel-epoll-native/src/test/resources/logging.properties b/netty-channel-epoll-native/src/test/resources/logging.properties new file mode 100644 index 0000000..3cd7309 --- /dev/null +++ b/netty-channel-epoll-native/src/test/resources/logging.properties @@ -0,0 +1,7 @@ +handlers=java.util.logging.ConsoleHandler +.level=ALL +java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +jdk.event.security.level=INFO +org.junit.jupiter.engine.execution.ConditionEvaluator.level=OFF diff --git a/netty-channel-sctp/build.gradle b/netty-channel-sctp/build.gradle new file mode 100644 index 0000000..d59a95f --- /dev/null +++ b/netty-channel-sctp/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':netty-channel') +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java new file mode 100644 index 0000000..616efdb --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpChannelConfig.java @@ -0,0 +1,245 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.SctpChannel; +import com.sun.nio.sctp.SctpStandardSocketOptions; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultChannelConfig; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.PlatformDependent; + +import java.io.IOException; +import java.util.Map; + +import static io.netty.channel.ChannelOption.SO_RCVBUF; +import static io.netty.channel.ChannelOption.SO_SNDBUF; +import static io.netty.channel.sctp.SctpChannelOption.SCTP_INIT_MAXSTREAMS; +import static io.netty.channel.sctp.SctpChannelOption.SCTP_NODELAY; + +/** + * The default {@link SctpChannelConfig} implementation for SCTP. + */ +public class DefaultSctpChannelConfig extends DefaultChannelConfig implements SctpChannelConfig { + + private final SctpChannel javaChannel; + + public DefaultSctpChannelConfig(io.netty.channel.sctp.SctpChannel channel, SctpChannel javaChannel) { + super(channel); + this.javaChannel = ObjectUtil.checkNotNull(javaChannel, "javaChannel"); + + // Enable TCP_NODELAY by default if possible. + if (PlatformDependent.canEnableTcpNoDelayByDefault()) { + try { + setSctpNoDelay(true); + } catch (Exception e) { + // Ignore. + } + } + } + + @Override + public Map, Object> getOptions() { + return getOptions( + super.getOptions(), + SO_RCVBUF, SO_SNDBUF, SCTP_NODELAY, SCTP_INIT_MAXSTREAMS); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == SO_SNDBUF) { + return (T) Integer.valueOf(getSendBufferSize()); + } + if (option == SCTP_NODELAY) { + return (T) Boolean.valueOf(isSctpNoDelay()); + } + if (option == SCTP_INIT_MAXSTREAMS) { + return (T) getInitMaxStreams(); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == SO_SNDBUF) { + setSendBufferSize((Integer) value); + } else if (option == SCTP_NODELAY) { + setSctpNoDelay((Boolean) value); + } else if (option == SCTP_INIT_MAXSTREAMS) { + setInitMaxStreams((SctpStandardSocketOptions.InitMaxStreams) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + @Override + public boolean isSctpNoDelay() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SCTP_NODELAY); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpChannelConfig setSctpNoDelay(boolean sctpNoDelay) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SCTP_NODELAY, sctpNoDelay); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public int getSendBufferSize() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SO_SNDBUF); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpChannelConfig setSendBufferSize(int sendBufferSize) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SO_SNDBUF, sendBufferSize); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public int getReceiveBufferSize() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SO_RCVBUF); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SO_RCVBUF, receiveBufferSize); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SctpStandardSocketOptions.InitMaxStreams getInitMaxStreams() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpChannelConfig setInitMaxStreams(SctpStandardSocketOptions.InitMaxStreams initMaxStreams) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS, initMaxStreams); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SctpChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + @Deprecated + public SctpChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public SctpChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public SctpChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public SctpChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public SctpChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public SctpChannelConfig setAutoClose(boolean autoClose) { + super.setAutoClose(autoClose); + return this; + } + + @Override + public SctpChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + public SctpChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public SctpChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public SctpChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java new file mode 100644 index 0000000..2860ba7 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/DefaultSctpServerChannelConfig.java @@ -0,0 +1,227 @@ +/* +* Copyright 2011 The Netty Project +* +* The Netty Project licenses this file to you 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: +* +* https://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. +*/ +package io.netty.channel.sctp; + +import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; + +import com.sun.nio.sctp.SctpServerChannel; +import com.sun.nio.sctp.SctpStandardSocketOptions; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultChannelConfig; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.ServerChannelRecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.util.NetUtil; +import io.netty.util.internal.ObjectUtil; + +import java.io.IOException; +import java.util.Map; + +/** + * The default {@link SctpServerChannelConfig} implementation for SCTP. + */ +public class DefaultSctpServerChannelConfig extends DefaultChannelConfig implements SctpServerChannelConfig { + + private final SctpServerChannel javaChannel; + private volatile int backlog = NetUtil.SOMAXCONN; + + /** + * Creates a new instance. + */ + public DefaultSctpServerChannelConfig( + io.netty.channel.sctp.SctpServerChannel channel, SctpServerChannel javaChannel) { + super(channel, new ServerChannelRecvByteBufAllocator()); + this.javaChannel = ObjectUtil.checkNotNull(javaChannel, "javaChannel"); + } + + @Override + public Map, Object> getOptions() { + return getOptions( + super.getOptions(), + ChannelOption.SO_RCVBUF, ChannelOption.SO_SNDBUF, SctpChannelOption.SCTP_INIT_MAXSTREAMS); + } + + @SuppressWarnings("unchecked") + @Override + public T getOption(ChannelOption option) { + if (option == ChannelOption.SO_RCVBUF) { + return (T) Integer.valueOf(getReceiveBufferSize()); + } + if (option == ChannelOption.SO_SNDBUF) { + return (T) Integer.valueOf(getSendBufferSize()); + } + if (option == SctpChannelOption.SCTP_INIT_MAXSTREAMS) { + return (T) getInitMaxStreams(); + } + return super.getOption(option); + } + + @Override + public boolean setOption(ChannelOption option, T value) { + validate(option, value); + + if (option == ChannelOption.SO_RCVBUF) { + setReceiveBufferSize((Integer) value); + } else if (option == ChannelOption.SO_SNDBUF) { + setSendBufferSize((Integer) value); + } else if (option == SctpChannelOption.SCTP_INIT_MAXSTREAMS) { + setInitMaxStreams((SctpStandardSocketOptions.InitMaxStreams) value); + } else { + return super.setOption(option, value); + } + + return true; + } + + @Override + public int getSendBufferSize() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SO_SNDBUF); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpServerChannelConfig setSendBufferSize(int sendBufferSize) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SO_SNDBUF, sendBufferSize); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public int getReceiveBufferSize() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SO_RCVBUF); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpServerChannelConfig setReceiveBufferSize(int receiveBufferSize) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SO_RCVBUF, receiveBufferSize); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public SctpStandardSocketOptions.InitMaxStreams getInitMaxStreams() { + try { + return javaChannel.getOption(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS); + } catch (IOException e) { + throw new ChannelException(e); + } + } + + @Override + public SctpServerChannelConfig setInitMaxStreams(SctpStandardSocketOptions.InitMaxStreams initMaxStreams) { + try { + javaChannel.setOption(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS, initMaxStreams); + } catch (IOException e) { + throw new ChannelException(e); + } + return this; + } + + @Override + public int getBacklog() { + return backlog; + } + + @Override + public SctpServerChannelConfig setBacklog(int backlog) { + checkPositiveOrZero(backlog, "backlog"); + this.backlog = backlog; + return this; + } + + @Override + @Deprecated + public SctpServerChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead) { + super.setMaxMessagesPerRead(maxMessagesPerRead); + return this; + } + + @Override + public SctpServerChannelConfig setWriteSpinCount(int writeSpinCount) { + super.setWriteSpinCount(writeSpinCount); + return this; + } + + @Override + public SctpServerChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { + super.setConnectTimeoutMillis(connectTimeoutMillis); + return this; + } + + @Override + public SctpServerChannelConfig setAllocator(ByteBufAllocator allocator) { + super.setAllocator(allocator); + return this; + } + + @Override + public SctpServerChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator) { + super.setRecvByteBufAllocator(allocator); + return this; + } + + @Override + public SctpServerChannelConfig setAutoRead(boolean autoRead) { + super.setAutoRead(autoRead); + return this; + } + + @Override + public SctpServerChannelConfig setAutoClose(boolean autoClose) { + super.setAutoClose(autoClose); + return this; + } + + @Override + public SctpServerChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + super.setWriteBufferLowWaterMark(writeBufferLowWaterMark); + return this; + } + + @Override + public SctpServerChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + super.setWriteBufferHighWaterMark(writeBufferHighWaterMark); + return this; + } + + @Override + public SctpServerChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + super.setWriteBufferWaterMark(writeBufferWaterMark); + return this; + } + + @Override + public SctpServerChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator) { + super.setMessageSizeEstimator(estimator); + return this; + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java new file mode 100644 index 0000000..725cf9e --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannel.java @@ -0,0 +1,114 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.Association; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Set; + +/** + * A SCTP/IP {@link Channel} interface for single SCTP association. + * + *

+ * The SctpChannel is a message-oriented, connected transport which supports multi-streaming and multi-homing. + *

+ */ +public interface SctpChannel extends Channel { + @Override + SctpServerChannel parent(); + + /** + * Returns the underlying SCTP association. + */ + Association association(); + + /** + * Return the (primary) local address of the SCTP channel. + * + * Please note that, this return the first local address in the underlying SCTP Channel's + * local address iterator to support Netty Channel API. In other words, its the application's + * responsibility to keep track of it's local primary address. + * + * (To set a local address as primary, the application can request by calling local SCTP stack, + * with SctpStandardSocketOption.SCTP_PRIMARY_ADDR option). + */ + @Override + InetSocketAddress localAddress(); + + /** + * Return all local addresses of the SCTP channel. + * Please note that, it will return more than one address if this channel is using multi-homing + */ + Set allLocalAddresses(); + + /** + * Returns the {@link SctpChannelConfig} configuration of the channel. + */ + @Override + SctpChannelConfig config(); + + /** + * Return the (primary) remote address of the SCTP channel. + * + * Please note that, this return the first remote address in the underlying SCTP Channel's + * remote address iterator to support Netty Channel API. In other words, its the application's + * responsibility to keep track of it's peer's primary address. + * + * (The application can request it's remote peer to set a specific address as primary by + * calling the local SCTP stack with SctpStandardSocketOption.SCTP_SET_PEER_PRIMARY_ADDR option) + */ + @Override + InetSocketAddress remoteAddress(); + + /** + * Return all remote addresses of the SCTP server channel. + * Please note that, it will return more than one address if the remote is using multi-homing. + */ + Set allRemoteAddresses(); + + /** + * Bind a address to the already bound channel to enable multi-homing. + * The Channel bust be bound and yet to be connected. + */ + ChannelFuture bindAddress(InetAddress localAddress); + + /** + * Bind a address to the already bound channel to enable multi-homing. + * The Channel bust be bound and yet to be connected. + * + * Will notify the given {@link ChannelPromise} and return a {@link ChannelFuture} + */ + ChannelFuture bindAddress(InetAddress localAddress, ChannelPromise promise); + + /** + * Unbind the address from channel's multi-homing address list. + * The address should be added already in multi-homing address list. + */ + ChannelFuture unbindAddress(InetAddress localAddress); + + /** + * Unbind the address from channel's multi-homing address list. + * The address should be added already in multi-homing address list. + * + * Will notify the given {@link ChannelPromise} and return a {@link ChannelFuture} + */ + ChannelFuture unbindAddress(InetAddress localAddress, ChannelPromise promise); +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java new file mode 100644 index 0000000..0c39a5a --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelConfig.java @@ -0,0 +1,135 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelOption; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; + +/** + * A {@link ChannelConfig} for a {@link SctpChannel}. + *

+ *

Available options

+ *

+ * In addition to the options provided by {@link ChannelConfig}, + * {@link SctpChannelConfig} allows the following options in the option map: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + *
NameAssociated setter method
{@link ChannelOption#SO_RCVBUF}{@link #setReceiveBufferSize(int)}
{@link ChannelOption#SO_SNDBUF}{@link #setSendBufferSize(int)}
{@link SctpChannelOption#SCTP_NODELAY}{@link #setSctpNoDelay(boolean)}}
{@link SctpChannelOption#SCTP_INIT_MAXSTREAMS}{@link #setInitMaxStreams(InitMaxStreams)}
+ */ +public interface SctpChannelConfig extends ChannelConfig { + + /** + * Gets the + * {@code SCTP_NODELAY} option. Please note that the default value of this option is {@code true} unlike the + * operating system default ({@code false}). However, for some buggy platforms, such as Android, that shows erratic + * behavior with Nagle's algorithm disabled, the default value remains to be {@code false}. + */ + boolean isSctpNoDelay(); + + /** + * Sets the + * {@code SCTP_NODELAY} option. Please note that the default value of this option is {@code true} unlike the + * operating system default ({@code false}). However, for some buggy platforms, such as Android, that shows erratic + * behavior with Nagle's algorithm disabled, the default value remains to be {@code false}. + */ + SctpChannelConfig setSctpNoDelay(boolean sctpNoDelay); + + /** + * Gets the + * {@code SO_SNDBUF} option. + */ + int getSendBufferSize(); + + /** + * Sets the + * {@code SO_SNDBUF} option. + */ + SctpChannelConfig setSendBufferSize(int sendBufferSize); + + /** + * Gets the + * {@code SO_RCVBUF} option. + */ + int getReceiveBufferSize(); + + /** + * Gets the + * {@code SO_RCVBUF} option. + */ + SctpChannelConfig setReceiveBufferSize(int receiveBufferSize); + + /** + * Gets the + * {@code SCTP_INIT_MAXSTREAMS} option. + */ + InitMaxStreams getInitMaxStreams(); + + /** + * Gets the + * {@code SCTP_INIT_MAXSTREAMS} option. + */ + SctpChannelConfig setInitMaxStreams(InitMaxStreams initMaxStreams); + + @Override + SctpChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); + + @Override + @Deprecated + SctpChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); + + @Override + SctpChannelConfig setWriteSpinCount(int writeSpinCount); + + @Override + SctpChannelConfig setAllocator(ByteBufAllocator allocator); + + @Override + SctpChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); + + @Override + SctpChannelConfig setAutoRead(boolean autoRead); + + @Override + SctpChannelConfig setAutoClose(boolean autoClose); + + @Override + SctpChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark); + + @Override + SctpChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark); + + @Override + SctpChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark); + + @Override + SctpChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator); +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelOption.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelOption.java new file mode 100644 index 0000000..0744857 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpChannelOption.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams; +import io.netty.channel.ChannelOption; + +import java.net.SocketAddress; + +/** + * Option for configuring the SCTP transport + */ +public final class SctpChannelOption extends ChannelOption { + + public static final ChannelOption SCTP_DISABLE_FRAGMENTS = + valueOf(SctpChannelOption.class, "SCTP_DISABLE_FRAGMENTS"); + public static final ChannelOption SCTP_EXPLICIT_COMPLETE = + valueOf(SctpChannelOption.class, "SCTP_EXPLICIT_COMPLETE"); + public static final ChannelOption SCTP_FRAGMENT_INTERLEAVE = + valueOf(SctpChannelOption.class, "SCTP_FRAGMENT_INTERLEAVE"); + public static final ChannelOption SCTP_INIT_MAXSTREAMS = + valueOf(SctpChannelOption.class, "SCTP_INIT_MAXSTREAMS"); + + public static final ChannelOption SCTP_NODELAY = + valueOf(SctpChannelOption.class, "SCTP_NODELAY"); + public static final ChannelOption SCTP_PRIMARY_ADDR = + valueOf(SctpChannelOption.class, "SCTP_PRIMARY_ADDR"); + public static final ChannelOption SCTP_SET_PEER_PRIMARY_ADDR = + valueOf(SctpChannelOption.class, "SCTP_SET_PEER_PRIMARY_ADDR"); + + @SuppressWarnings({ "unused", "deprecation" }) + private SctpChannelOption() { + super(null); + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpMessage.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpMessage.java new file mode 100644 index 0000000..e4b18a0 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpMessage.java @@ -0,0 +1,204 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.MessageInfo; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; +import io.netty.util.internal.ObjectUtil; + +/** + * Representation of SCTP Data Chunk + */ +public final class SctpMessage extends DefaultByteBufHolder { + private final int streamIdentifier; + private final int protocolIdentifier; + private final boolean unordered; + + private final MessageInfo msgInfo; + + /** + * Essential data that is being carried within SCTP Data Chunk + * @param protocolIdentifier of payload + * @param streamIdentifier that you want to send the payload + * @param payloadBuffer channel buffer + */ + public SctpMessage(int protocolIdentifier, int streamIdentifier, ByteBuf payloadBuffer) { + this(protocolIdentifier, streamIdentifier, false, payloadBuffer); + } + + /** + * Essential data that is being carried within SCTP Data Chunk + * @param protocolIdentifier of payload + * @param streamIdentifier that you want to send the payload + * @param unordered if {@literal true}, the SCTP Data Chunk will be sent with the U (unordered) flag set. + * @param payloadBuffer channel buffer + */ + public SctpMessage(int protocolIdentifier, int streamIdentifier, boolean unordered, ByteBuf payloadBuffer) { + super(payloadBuffer); + this.protocolIdentifier = protocolIdentifier; + this.streamIdentifier = streamIdentifier; + this.unordered = unordered; + msgInfo = null; + } + + /** + * Essential data that is being carried within SCTP Data Chunk + * @param msgInfo the {@link MessageInfo} + * @param payloadBuffer channel buffer + */ + public SctpMessage(MessageInfo msgInfo, ByteBuf payloadBuffer) { + super(payloadBuffer); + this.msgInfo = ObjectUtil.checkNotNull(msgInfo, "msgInfo"); + this.streamIdentifier = msgInfo.streamNumber(); + this.protocolIdentifier = msgInfo.payloadProtocolID(); + this.unordered = msgInfo.isUnordered(); + } + + /** + * Return the stream-identifier + */ + public int streamIdentifier() { + return streamIdentifier; + } + + /** + * Return the protocol-identifier + */ + public int protocolIdentifier() { + return protocolIdentifier; + } + + /** + * return the unordered flag + */ + public boolean isUnordered() { + return unordered; + } + + /** + * Return the {@link MessageInfo} for inbound messages or {@code null} for + * outbound messages. + */ + public MessageInfo messageInfo() { + return msgInfo; + } + + /** + * Return {@code true} if this message is complete. + */ + public boolean isComplete() { + if (msgInfo != null) { + return msgInfo.isComplete(); + } else { + //all outbound sctp messages are complete + return true; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + SctpMessage sctpFrame = (SctpMessage) o; + + if (protocolIdentifier != sctpFrame.protocolIdentifier) { + return false; + } + + if (streamIdentifier != sctpFrame.streamIdentifier) { + return false; + } + + if (unordered != sctpFrame.unordered) { + return false; + } + + return content().equals(sctpFrame.content()); + } + + @Override + public int hashCode() { + int result = streamIdentifier; + result = 31 * result + protocolIdentifier; + // values 1231 and 1237 are referenced in the javadocs of Boolean#hashCode() + result = 31 * result + (unordered ? 1231 : 1237); + result = 31 * result + content().hashCode(); + return result; + } + + @Override + public SctpMessage copy() { + return (SctpMessage) super.copy(); + } + + @Override + public SctpMessage duplicate() { + return (SctpMessage) super.duplicate(); + } + + @Override + public SctpMessage retainedDuplicate() { + return (SctpMessage) super.retainedDuplicate(); + } + + @Override + public SctpMessage replace(ByteBuf content) { + if (msgInfo == null) { + return new SctpMessage(protocolIdentifier, streamIdentifier, unordered, content); + } else { + return new SctpMessage(msgInfo, content); + } + } + + @Override + public SctpMessage retain() { + super.retain(); + return this; + } + + @Override + public SctpMessage retain(int increment) { + super.retain(increment); + return this; + } + + @Override + public SctpMessage touch() { + super.touch(); + return this; + } + + @Override + public SctpMessage touch(Object hint) { + super.touch(hint); + return this; + } + + @Override + public String toString() { + return "SctpFrame{" + + "streamIdentifier=" + streamIdentifier + ", protocolIdentifier=" + protocolIdentifier + + ", unordered=" + unordered + + ", data=" + contentToString() + '}'; + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java new file mode 100644 index 0000000..6689d6c --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpNotificationHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.AbstractNotificationHandler; +import com.sun.nio.sctp.AssociationChangeNotification; +import com.sun.nio.sctp.HandlerResult; +import com.sun.nio.sctp.Notification; +import com.sun.nio.sctp.PeerAddressChangeNotification; +import com.sun.nio.sctp.SendFailedNotification; +import com.sun.nio.sctp.ShutdownNotification; +import io.netty.channel.ChannelPipeline; +import io.netty.util.internal.ObjectUtil; + + +/** + * {@link AbstractNotificationHandler} implementation which will handle all {@link Notification}s by trigger a + * {@link Notification} user event in the {@link ChannelPipeline} of a {@link SctpChannel}. + */ +public final class SctpNotificationHandler extends AbstractNotificationHandler { + + private final SctpChannel sctpChannel; + + public SctpNotificationHandler(SctpChannel sctpChannel) { + this.sctpChannel = ObjectUtil.checkNotNull(sctpChannel, "sctpChannel"); + } + + @Override + public HandlerResult handleNotification(AssociationChangeNotification notification, Object o) { + fireEvent(notification); + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, Object o) { + fireEvent(notification); + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(SendFailedNotification notification, Object o) { + fireEvent(notification); + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(ShutdownNotification notification, Object o) { + fireEvent(notification); + sctpChannel.close(); + return HandlerResult.RETURN; + } + + private void fireEvent(Notification notification) { + sctpChannel.pipeline().fireUserEventTriggered(notification); + } +} + diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java new file mode 100644 index 0000000..69ff955 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannel.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import io.netty.channel.ServerChannel; + +import java.net.InetSocketAddress; +import java.net.InetAddress; +import java.util.Set; + +/** + * A SCTP/IP {@link ServerChannel} which accepts incoming SCTP/IP associations. + * + *

+ * Multi-homing address binding/unbinding can done through bindAddress/unbindAddress methods. + *

+ */ +public interface SctpServerChannel extends ServerChannel { + + /** + * Returns the {@link SctpServerChannelConfig} configuration of the channel. + */ + @Override + SctpServerChannelConfig config(); + + /** + * Return the (primary) local address of the SCTP server channel. + * + * Please note that, this return the first local address in the underlying SCTP ServerChannel's + * local address iterator to support Netty Channel API. In other words, its the application's + * responsibility to keep track of it's local primary address. + * + * (To set a local address as primary, the application can request by calling local SCTP stack, + * with SctpStandardSocketOption.SCTP_PRIMARY_ADDR option). + */ + @Override + InetSocketAddress localAddress(); + + /** + * Return all local addresses of the SCTP server channel. + * Please note that, it will return more than one address if this channel is using multi-homing + */ + Set allLocalAddresses(); + + /** + * Bind a address to the already bound channel to enable multi-homing. + * The Channel must be bound and yet to be connected. + */ + ChannelFuture bindAddress(InetAddress localAddress); + + /** + * Bind a address to the already bound channel to enable multi-homing. + * The Channel must be bound and yet to be connected. + * + * Will notify the given {@link ChannelPromise} and return a {@link ChannelFuture} + */ + ChannelFuture bindAddress(InetAddress localAddress, ChannelPromise promise); + + /** + * Unbind the address from channel's multi-homing address list. + * The address should be added already in multi-homing address list. + */ + ChannelFuture unbindAddress(InetAddress localAddress); + + /** + * Unbind the address from channel's multi-homing address list. + * The address should be added already in multi-homing address list. + * + * Will notify the given {@link ChannelPromise} and return a {@link ChannelFuture} + */ + ChannelFuture unbindAddress(InetAddress localAddress, ChannelPromise promise); +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelConfig.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelConfig.java new file mode 100644 index 0000000..45411a6 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/SctpServerChannelConfig.java @@ -0,0 +1,130 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp; + +import com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelOption; +import io.netty.channel.MessageSizeEstimator; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.WriteBufferWaterMark; + +/** + * A {@link ChannelConfig} for a {@link SctpServerChannelConfig}. + *

+ *

Available options

+ *

+ * In addition to the options provided by {@link ChannelConfig}, + * {@link SctpServerChannelConfig} allows the following options in the + * option map: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + *
NameAssociated setter method
{@link ChannelOption#SO_BACKLOG}{@link #setBacklog(int)}
{@link ChannelOption#SO_RCVBUF}{@link #setReceiveBufferSize(int)}
{@link ChannelOption#SO_SNDBUF}{@link #setSendBufferSize(int)}
{@link SctpChannelOption#SCTP_INIT_MAXSTREAMS}{@link #setInitMaxStreams(InitMaxStreams)}
+ */ +public interface SctpServerChannelConfig extends ChannelConfig { + + /** + * Gets the backlog value to specify when the channel binds to a local address. + */ + int getBacklog(); + + /** + * Sets the backlog value to specify when the channel binds to a local address. + */ + SctpServerChannelConfig setBacklog(int backlog); + + /** + * Gets the + * {@code SO_SNDBUF} option. + */ + int getSendBufferSize(); + + /** + * Sets the + * {@code SO_SNDBUF} option. + */ + SctpServerChannelConfig setSendBufferSize(int sendBufferSize); + + /** + * Gets the + * {@code SO_RCVBUF} option. + */ + int getReceiveBufferSize(); + + /** + * Gets the + * {@code SO_RCVBUF} option. + */ + SctpServerChannelConfig setReceiveBufferSize(int receiveBufferSize); + + /** + * Gets the + * {@code SCTP_INIT_MAXSTREAMS} option. + */ + InitMaxStreams getInitMaxStreams(); + + /** + * Gets the + * {@code SCTP_INIT_MAXSTREAMS} option. + */ + SctpServerChannelConfig setInitMaxStreams(InitMaxStreams initMaxStreams); + + @Override + @Deprecated + SctpServerChannelConfig setMaxMessagesPerRead(int maxMessagesPerRead); + + @Override + SctpServerChannelConfig setWriteSpinCount(int writeSpinCount); + + @Override + SctpServerChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); + + @Override + SctpServerChannelConfig setAllocator(ByteBufAllocator allocator); + + @Override + SctpServerChannelConfig setRecvByteBufAllocator(RecvByteBufAllocator allocator); + + @Override + SctpServerChannelConfig setAutoRead(boolean autoRead); + + @Override + SctpServerChannelConfig setAutoClose(boolean autoClose); + + @Override + SctpServerChannelConfig setWriteBufferHighWaterMark(int writeBufferHighWaterMark); + + @Override + SctpServerChannelConfig setWriteBufferLowWaterMark(int writeBufferLowWaterMark); + + @Override + SctpServerChannelConfig setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark); + + @Override + SctpServerChannelConfig setMessageSizeEstimator(MessageSizeEstimator estimator); +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpChannel.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpChannel.java new file mode 100644 index 0000000..62ba8a4 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpChannel.java @@ -0,0 +1,401 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp.nio; + +import com.sun.nio.sctp.Association; +import com.sun.nio.sctp.MessageInfo; +import com.sun.nio.sctp.NotificationHandler; +import com.sun.nio.sctp.SctpChannel; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPromise; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.nio.AbstractNioMessageChannel; +import io.netty.channel.sctp.DefaultSctpChannelConfig; +import io.netty.channel.sctp.SctpChannelConfig; +import io.netty.channel.sctp.SctpMessage; +import io.netty.channel.sctp.SctpNotificationHandler; +import io.netty.channel.sctp.SctpServerChannel; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link io.netty.channel.sctp.SctpChannel} implementation which use non-blocking mode and allows to read / + * write {@link SctpMessage}s to the underlying {@link SctpChannel}. + * + * Be aware that not all operations systems support SCTP. Please refer to the documentation of your operation system, + * to understand what you need to do to use it. Also this feature is only supported on Java 7+. + */ +public class NioSctpChannel extends AbstractNioMessageChannel implements io.netty.channel.sctp.SctpChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioSctpChannel.class); + + private final SctpChannelConfig config; + + private final NotificationHandler notificationHandler; + + private static SctpChannel newSctpChannel() { + try { + return SctpChannel.open(); + } catch (IOException e) { + throw new ChannelException("Failed to open a sctp channel.", e); + } + } + + /** + * Create a new instance + */ + public NioSctpChannel() { + this(newSctpChannel()); + } + + /** + * Create a new instance using {@link SctpChannel} + */ + public NioSctpChannel(SctpChannel sctpChannel) { + this(null, sctpChannel); + } + + /** + * Create a new instance + * + * @param parent the {@link Channel} which is the parent of this {@link NioSctpChannel} + * or {@code null}. + * @param sctpChannel the underlying {@link SctpChannel} + */ + public NioSctpChannel(Channel parent, SctpChannel sctpChannel) { + super(parent, sctpChannel, SelectionKey.OP_READ); + try { + sctpChannel.configureBlocking(false); + config = new NioSctpChannelConfig(this, sctpChannel); + notificationHandler = new SctpNotificationHandler(this); + } catch (IOException e) { + try { + sctpChannel.close(); + } catch (IOException e2) { + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized sctp channel.", e2); + } + } + + throw new ChannelException("Failed to enter non-blocking mode.", e); + } + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public InetSocketAddress remoteAddress() { + return (InetSocketAddress) super.remoteAddress(); + } + + @Override + public SctpServerChannel parent() { + return (SctpServerChannel) super.parent(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public Association association() { + try { + return javaChannel().association(); + } catch (IOException ignored) { + return null; + } + } + + @Override + public Set allLocalAddresses() { + try { + final Set allLocalAddresses = javaChannel().getAllLocalAddresses(); + final Set addresses = new LinkedHashSet(allLocalAddresses.size()); + for (SocketAddress socketAddress : allLocalAddresses) { + addresses.add((InetSocketAddress) socketAddress); + } + return addresses; + } catch (Throwable ignored) { + return Collections.emptySet(); + } + } + + @Override + public SctpChannelConfig config() { + return config; + } + + @Override + public Set allRemoteAddresses() { + try { + final Set allLocalAddresses = javaChannel().getRemoteAddresses(); + final Set addresses = new HashSet(allLocalAddresses.size()); + for (SocketAddress socketAddress : allLocalAddresses) { + addresses.add((InetSocketAddress) socketAddress); + } + return addresses; + } catch (Throwable ignored) { + return Collections.emptySet(); + } + } + + @Override + protected SctpChannel javaChannel() { + return (SctpChannel) super.javaChannel(); + } + + @Override + public boolean isActive() { + SctpChannel ch = javaChannel(); + return ch.isOpen() && association() != null; + } + + @Override + protected SocketAddress localAddress0() { + try { + Iterator i = javaChannel().getAllLocalAddresses().iterator(); + if (i.hasNext()) { + return i.next(); + } + } catch (IOException e) { + // ignore + } + return null; + } + + @Override + protected SocketAddress remoteAddress0() { + try { + Iterator i = javaChannel().getRemoteAddresses().iterator(); + if (i.hasNext()) { + return i.next(); + } + } catch (IOException e) { + // ignore + } + return null; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + javaChannel().bind(localAddress); + } + + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (localAddress != null) { + javaChannel().bind(localAddress); + } + + boolean success = false; + try { + boolean connected = javaChannel().connect(remoteAddress); + if (!connected) { + selectionKey().interestOps(SelectionKey.OP_CONNECT); + } + success = true; + return connected; + } finally { + if (!success) { + doClose(); + } + } + } + + @Override + protected void doFinishConnect() throws Exception { + if (!javaChannel().finishConnect()) { + throw new Error(); + } + } + + @Override + protected void doDisconnect() throws Exception { + doClose(); + } + + @Override + protected void doClose() throws Exception { + javaChannel().close(); + } + + @Override + protected int doReadMessages(List buf) throws Exception { + SctpChannel ch = javaChannel(); + + RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); + ByteBuf buffer = allocHandle.allocate(config().getAllocator()); + boolean free = true; + try { + ByteBuffer data = buffer.internalNioBuffer(buffer.writerIndex(), buffer.writableBytes()); + int pos = data.position(); + + MessageInfo messageInfo = ch.receive(data, null, notificationHandler); + if (messageInfo == null) { + return 0; + } + + allocHandle.lastBytesRead(data.position() - pos); + buf.add(new SctpMessage(messageInfo, + buffer.writerIndex(buffer.writerIndex() + allocHandle.lastBytesRead()))); + free = false; + return 1; + } catch (Throwable cause) { + PlatformDependent.throwException(cause); + return -1; + } finally { + if (free) { + buffer.release(); + } + } + } + + @Override + protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception { + SctpMessage packet = (SctpMessage) msg; + ByteBuf data = packet.content(); + int dataLen = data.readableBytes(); + if (dataLen == 0) { + return true; + } + + ByteBufAllocator alloc = alloc(); + boolean needsCopy = data.nioBufferCount() != 1; + if (!needsCopy) { + if (!data.isDirect() && alloc.isDirectBufferPooled()) { + needsCopy = true; + } + } + ByteBuffer nioData; + if (needsCopy) { + data = alloc.directBuffer(dataLen).writeBytes(data); + } + nioData = data.nioBuffer(); + final MessageInfo mi = MessageInfo.createOutgoing(association(), null, packet.streamIdentifier()); + mi.payloadProtocolID(packet.protocolIdentifier()); + mi.streamNumber(packet.streamIdentifier()); + mi.unordered(packet.isUnordered()); + + final int writtenBytes = javaChannel().send(nioData, mi); + return writtenBytes > 0; + } + + @Override + protected final Object filterOutboundMessage(Object msg) throws Exception { + if (msg instanceof SctpMessage) { + SctpMessage m = (SctpMessage) msg; + ByteBuf buf = m.content(); + if (buf.isDirect() && buf.nioBufferCount() == 1) { + return m; + } + + return new SctpMessage(m.protocolIdentifier(), m.streamIdentifier(), m.isUnordered(), + newDirectBuffer(m, buf)); + } + + throw new UnsupportedOperationException( + "unsupported message type: " + StringUtil.simpleClassName(msg) + + " (expected: " + StringUtil.simpleClassName(SctpMessage.class)); + } + + @Override + public ChannelFuture bindAddress(InetAddress localAddress) { + return bindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture bindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + javaChannel().bindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + bindAddress(localAddress, promise); + } + }); + } + return promise; + } + + @Override + public ChannelFuture unbindAddress(InetAddress localAddress) { + return unbindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture unbindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + javaChannel().unbindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + unbindAddress(localAddress, promise); + } + }); + } + return promise; + } + + private final class NioSctpChannelConfig extends DefaultSctpChannelConfig { + private NioSctpChannelConfig(NioSctpChannel channel, SctpChannel javaChannel) { + super(channel, javaChannel); + } + + @Override + protected void autoReadCleared() { + clearReadPending(); + } + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpServerChannel.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpServerChannel.java new file mode 100644 index 0000000..4a093bc --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/NioSctpServerChannel.java @@ -0,0 +1,239 @@ +/* + * Copyright 2011 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp.nio; + +import com.sun.nio.sctp.SctpChannel; +import com.sun.nio.sctp.SctpServerChannel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPromise; +import io.netty.channel.nio.AbstractNioMessageChannel; +import io.netty.channel.sctp.DefaultSctpServerChannelConfig; +import io.netty.channel.sctp.SctpServerChannelConfig; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link io.netty.channel.sctp.SctpServerChannel} implementation which use non-blocking mode to accept new + * connections and create the {@link NioSctpChannel} for them. + * + * Be aware that not all operations systems support SCTP. Please refer to the documentation of your operation system, + * to understand what you need to do to use it. Also this feature is only supported on Java 7+. + */ +public class NioSctpServerChannel extends AbstractNioMessageChannel + implements io.netty.channel.sctp.SctpServerChannel { + private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16); + + private static SctpServerChannel newSocket() { + try { + return SctpServerChannel.open(); + } catch (IOException e) { + throw new ChannelException( + "Failed to open a server socket.", e); + } + } + + private final SctpServerChannelConfig config; + + /** + * Create a new instance + */ + public NioSctpServerChannel() { + super(null, newSocket(), SelectionKey.OP_ACCEPT); + config = new NioSctpServerChannelConfig(this, javaChannel()); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public Set allLocalAddresses() { + try { + final Set allLocalAddresses = javaChannel().getAllLocalAddresses(); + final Set addresses = new LinkedHashSet(allLocalAddresses.size()); + for (SocketAddress socketAddress : allLocalAddresses) { + addresses.add((InetSocketAddress) socketAddress); + } + return addresses; + } catch (Throwable ignored) { + return Collections.emptySet(); + } + } + + @Override + public SctpServerChannelConfig config() { + return config; + } + + @Override + public boolean isActive() { + return isOpen() && !allLocalAddresses().isEmpty(); + } + + @Override + public InetSocketAddress remoteAddress() { + return null; + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + protected SctpServerChannel javaChannel() { + return (SctpServerChannel) super.javaChannel(); + } + + @Override + protected SocketAddress localAddress0() { + try { + Iterator i = javaChannel().getAllLocalAddresses().iterator(); + if (i.hasNext()) { + return i.next(); + } + } catch (IOException e) { + // ignore + } + return null; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + javaChannel().bind(localAddress, config.getBacklog()); + } + + @Override + protected void doClose() throws Exception { + javaChannel().close(); + } + + @Override + protected int doReadMessages(List buf) throws Exception { + SctpChannel ch = javaChannel().accept(); + if (ch == null) { + return 0; + } + buf.add(new NioSctpChannel(this, ch)); + return 1; + } + + @Override + public ChannelFuture bindAddress(InetAddress localAddress) { + return bindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture bindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + javaChannel().bindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + bindAddress(localAddress, promise); + } + }); + } + return promise; + } + + @Override + public ChannelFuture unbindAddress(InetAddress localAddress) { + return unbindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture unbindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + javaChannel().unbindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + unbindAddress(localAddress, promise); + } + }); + } + return promise; + } + + // Unnecessary stuff + @Override + protected boolean doConnect( + SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected void doFinishConnect() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected SocketAddress remoteAddress0() { + return null; + } + + @Override + protected void doDisconnect() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected Object filterOutboundMessage(Object msg) throws Exception { + throw new UnsupportedOperationException(); + } + + private final class NioSctpServerChannelConfig extends DefaultSctpServerChannelConfig { + private NioSctpServerChannelConfig(NioSctpServerChannel channel, SctpServerChannel javaChannel) { + super(channel, javaChannel); + } + + @Override + protected void autoReadCleared() { + clearReadPending(); + } + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/package-info.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/package-info.java new file mode 100644 index 0000000..52d1830 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/nio/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * NIO-based SCTP Channel + * API implementation - recommended for a large number of connections (>= 1000). + */ +package io.netty.channel.sctp.nio; diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpChannel.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpChannel.java new file mode 100755 index 0000000..fd7c30a --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpChannel.java @@ -0,0 +1,474 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp.oio; + +import com.sun.nio.sctp.Association; +import com.sun.nio.sctp.MessageInfo; +import com.sun.nio.sctp.NotificationHandler; +import com.sun.nio.sctp.SctpChannel; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPromise; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.oio.AbstractOioMessageChannel; +import io.netty.channel.sctp.DefaultSctpChannelConfig; +import io.netty.channel.sctp.SctpChannelConfig; +import io.netty.channel.sctp.SctpMessage; +import io.netty.channel.sctp.SctpNotificationHandler; +import io.netty.channel.sctp.SctpServerChannel; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link io.netty.channel.sctp.SctpChannel} implementation which use blocking mode and allows to read / write + * {@link SctpMessage}s to the underlying {@link SctpChannel}. + * + * Be aware that not all operations systems support SCTP. Please refer to the documentation of your operation system, + * to understand what you need to do to use it. Also this feature is only supported on Java 7+. + * + * @deprecated use {@link io.netty.channel.sctp.nio.NioSctpChannel}. + */ +@Deprecated +public class OioSctpChannel extends AbstractOioMessageChannel + implements io.netty.channel.sctp.SctpChannel { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(OioSctpChannel.class); + + private static final ChannelMetadata METADATA = new ChannelMetadata(false); + private static final String EXPECTED_TYPE = " (expected: " + StringUtil.simpleClassName(SctpMessage.class) + ')'; + + private final SctpChannel ch; + private final SctpChannelConfig config; + + private final Selector readSelector; + private final Selector writeSelector; + private final Selector connectSelector; + + private final NotificationHandler notificationHandler; + + private static SctpChannel openChannel() { + try { + return SctpChannel.open(); + } catch (IOException e) { + throw new ChannelException("Failed to open a sctp channel.", e); + } + } + + /** + * Create a new instance with an new {@link SctpChannel}. + */ + public OioSctpChannel() { + this(openChannel()); + } + + /** + * Create a new instance from the given {@link SctpChannel}. + * + * @param ch the {@link SctpChannel} which is used by this instance + */ + public OioSctpChannel(SctpChannel ch) { + this(null, ch); + } + + /** + * Create a new instance from the given {@link SctpChannel}. + * + * @param parent the parent {@link Channel} which was used to create this instance. This can be null if the + * {@link} has no parent as it was created by your self. + * @param ch the {@link SctpChannel} which is used by this instance + */ + public OioSctpChannel(Channel parent, SctpChannel ch) { + super(parent); + this.ch = ch; + boolean success = false; + try { + ch.configureBlocking(false); + readSelector = Selector.open(); + writeSelector = Selector.open(); + connectSelector = Selector.open(); + + ch.register(readSelector, SelectionKey.OP_READ); + ch.register(writeSelector, SelectionKey.OP_WRITE); + ch.register(connectSelector, SelectionKey.OP_CONNECT); + + config = new OioSctpChannelConfig(this, ch); + notificationHandler = new SctpNotificationHandler(this); + success = true; + } catch (Exception e) { + throw new ChannelException("failed to initialize a sctp channel", e); + } finally { + if (!success) { + try { + ch.close(); + } catch (IOException e) { + logger.warn("Failed to close a sctp channel.", e); + } + } + } + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public InetSocketAddress remoteAddress() { + return (InetSocketAddress) super.remoteAddress(); + } + + @Override + public SctpServerChannel parent() { + return (SctpServerChannel) super.parent(); + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public SctpChannelConfig config() { + return config; + } + + @Override + public boolean isOpen() { + return ch.isOpen(); + } + + @Override + protected int doReadMessages(List msgs) throws Exception { + if (!readSelector.isOpen()) { + return 0; + } + + int readMessages = 0; + + final int selectedKeys = readSelector.select(SO_TIMEOUT); + final boolean keysSelected = selectedKeys > 0; + + if (!keysSelected) { + return readMessages; + } + // We must clear the selectedKeys because the Selector will never do it. If we do not clear it, the selectionKey + // will always be returned even if there is no data can be read which causes performance issue. And in some + // implementation of Selector, the select method may return 0 if the selectionKey which is ready for process has + // already been in the selectedKeys and cause the keysSelected above to be false even if we actually have + // something to read. + readSelector.selectedKeys().clear(); + final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); + ByteBuf buffer = allocHandle.allocate(config().getAllocator()); + boolean free = true; + + try { + ByteBuffer data = buffer.nioBuffer(buffer.writerIndex(), buffer.writableBytes()); + MessageInfo messageInfo = ch.receive(data, null, notificationHandler); + if (messageInfo == null) { + return readMessages; + } + + data.flip(); + allocHandle.lastBytesRead(data.remaining()); + msgs.add(new SctpMessage(messageInfo, + buffer.writerIndex(buffer.writerIndex() + allocHandle.lastBytesRead()))); + free = false; + ++readMessages; + } catch (Throwable cause) { + PlatformDependent.throwException(cause); + } finally { + if (free) { + buffer.release(); + } + } + return readMessages; + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + if (!writeSelector.isOpen()) { + return; + } + final int size = in.size(); + final int selectedKeys = writeSelector.select(SO_TIMEOUT); + if (selectedKeys > 0) { + final Set writableKeys = writeSelector.selectedKeys(); + if (writableKeys.isEmpty()) { + return; + } + Iterator writableKeysIt = writableKeys.iterator(); + int written = 0; + for (;;) { + if (written == size) { + // all written + return; + } + writableKeysIt.next(); + writableKeysIt.remove(); + + SctpMessage packet = (SctpMessage) in.current(); + if (packet == null) { + return; + } + + ByteBuf data = packet.content(); + int dataLen = data.readableBytes(); + ByteBuffer nioData; + + if (data.nioBufferCount() != -1) { + nioData = data.nioBuffer(); + } else { + nioData = ByteBuffer.allocate(dataLen); + data.getBytes(data.readerIndex(), nioData); + nioData.flip(); + } + + final MessageInfo mi = MessageInfo.createOutgoing(association(), null, packet.streamIdentifier()); + mi.payloadProtocolID(packet.protocolIdentifier()); + mi.streamNumber(packet.streamIdentifier()); + mi.unordered(packet.isUnordered()); + + ch.send(nioData, mi); + written ++; + in.remove(); + + if (!writableKeysIt.hasNext()) { + return; + } + } + } + } + + @Override + protected Object filterOutboundMessage(Object msg) throws Exception { + if (msg instanceof SctpMessage) { + return msg; + } + + throw new UnsupportedOperationException( + "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPE); + } + + @Override + public Association association() { + try { + return ch.association(); + } catch (IOException ignored) { + return null; + } + } + + @Override + public boolean isActive() { + return isOpen() && association() != null; + } + + @Override + protected SocketAddress localAddress0() { + try { + Iterator i = ch.getAllLocalAddresses().iterator(); + if (i.hasNext()) { + return i.next(); + } + } catch (IOException e) { + // ignore + } + return null; + } + + @Override + public Set allLocalAddresses() { + try { + final Set allLocalAddresses = ch.getAllLocalAddresses(); + final Set addresses = new LinkedHashSet(allLocalAddresses.size()); + for (SocketAddress socketAddress : allLocalAddresses) { + addresses.add((InetSocketAddress) socketAddress); + } + return addresses; + } catch (Throwable ignored) { + return Collections.emptySet(); + } + } + + @Override + protected SocketAddress remoteAddress0() { + try { + Iterator i = ch.getRemoteAddresses().iterator(); + if (i.hasNext()) { + return i.next(); + } + } catch (IOException e) { + // ignore + } + return null; + } + + @Override + public Set allRemoteAddresses() { + try { + final Set allLocalAddresses = ch.getRemoteAddresses(); + final Set addresses = new LinkedHashSet(allLocalAddresses.size()); + for (SocketAddress socketAddress : allLocalAddresses) { + addresses.add((InetSocketAddress) socketAddress); + } + return addresses; + } catch (Throwable ignored) { + return Collections.emptySet(); + } + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + ch.bind(localAddress); + } + + @Override + protected void doConnect(SocketAddress remoteAddress, + SocketAddress localAddress) throws Exception { + if (localAddress != null) { + ch.bind(localAddress); + } + + boolean success = false; + try { + ch.connect(remoteAddress); + boolean finishConnect = false; + while (!finishConnect) { + if (connectSelector.select(SO_TIMEOUT) >= 0) { + final Set selectionKeys = connectSelector.selectedKeys(); + for (SelectionKey key : selectionKeys) { + if (key.isConnectable()) { + selectionKeys.clear(); + finishConnect = true; + break; + } + } + selectionKeys.clear(); + } + } + success = ch.finishConnect(); + } finally { + if (!success) { + doClose(); + } + } + } + + @Override + protected void doDisconnect() throws Exception { + doClose(); + } + + @Override + protected void doClose() throws Exception { + closeSelector("read", readSelector); + closeSelector("write", writeSelector); + closeSelector("connect", connectSelector); + ch.close(); + } + + private static void closeSelector(String selectorName, Selector selector) { + try { + selector.close(); + } catch (IOException e) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to close a " + selectorName + " selector.", e); + } + } + } + + @Override + public ChannelFuture bindAddress(InetAddress localAddress) { + return bindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture bindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + ch.bindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + bindAddress(localAddress, promise); + } + }); + } + return promise; + } + + @Override + public ChannelFuture unbindAddress(InetAddress localAddress) { + return unbindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture unbindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + ch.unbindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + unbindAddress(localAddress, promise); + } + }); + } + return promise; + } + + private final class OioSctpChannelConfig extends DefaultSctpChannelConfig { + private OioSctpChannelConfig(OioSctpChannel channel, SctpChannel javaChannel) { + super(channel, javaChannel); + } + + @Override + protected void autoReadCleared() { + clearReadPending(); + } + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpServerChannel.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpServerChannel.java new file mode 100755 index 0000000..d470ad6 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/OioSctpServerChannel.java @@ -0,0 +1,308 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.channel.sctp.oio; + +import com.sun.nio.sctp.SctpChannel; +import com.sun.nio.sctp.SctpServerChannel; +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelOutboundBuffer; +import io.netty.channel.ChannelPromise; +import io.netty.channel.oio.AbstractOioMessageChannel; +import io.netty.channel.sctp.DefaultSctpServerChannelConfig; +import io.netty.channel.sctp.SctpServerChannelConfig; +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * {@link io.netty.channel.sctp.SctpServerChannel} implementation which use blocking mode to accept new + * connections and create the {@link OioSctpChannel} for them. + * + * Be aware that not all operations systems support SCTP. Please refer to the documentation of your operation system, + * to understand what you need to do to use it. Also this feature is only supported on Java 7+. + * + * @deprecated use {@link io.netty.channel.sctp.nio.NioSctpServerChannel}. + */ +@Deprecated +public class OioSctpServerChannel extends AbstractOioMessageChannel + implements io.netty.channel.sctp.SctpServerChannel { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(OioSctpServerChannel.class); + + private static final ChannelMetadata METADATA = new ChannelMetadata(false, 1); + + private static SctpServerChannel newServerSocket() { + try { + return SctpServerChannel.open(); + } catch (IOException e) { + throw new ChannelException("failed to create a sctp server channel", e); + } + } + + private final SctpServerChannel sch; + private final SctpServerChannelConfig config; + private final Selector selector; + + /** + * Create a new instance with an new {@link SctpServerChannel} + */ + public OioSctpServerChannel() { + this(newServerSocket()); + } + + /** + * Create a new instance from the given {@link SctpServerChannel} + * + * @param sch the {@link SctpServerChannel} which is used by this instance + */ + public OioSctpServerChannel(SctpServerChannel sch) { + super(null); + this.sch = ObjectUtil.checkNotNull(sch, "sctp server channel"); + boolean success = false; + try { + sch.configureBlocking(false); + selector = Selector.open(); + sch.register(selector, SelectionKey.OP_ACCEPT); + config = new OioSctpServerChannelConfig(this, sch); + success = true; + } catch (Exception e) { + throw new ChannelException("failed to initialize a sctp server channel", e); + } finally { + if (!success) { + try { + sch.close(); + } catch (IOException e) { + logger.warn("Failed to close a sctp server channel.", e); + } + } + } + } + + @Override + public ChannelMetadata metadata() { + return METADATA; + } + + @Override + public SctpServerChannelConfig config() { + return config; + } + + @Override + public InetSocketAddress remoteAddress() { + return null; + } + + @Override + public InetSocketAddress localAddress() { + return (InetSocketAddress) super.localAddress(); + } + + @Override + public boolean isOpen() { + return sch.isOpen(); + } + + @Override + protected SocketAddress localAddress0() { + try { + Iterator i = sch.getAllLocalAddresses().iterator(); + if (i.hasNext()) { + return i.next(); + } + } catch (IOException e) { + // ignore + } + return null; + } + + @Override + public Set allLocalAddresses() { + try { + final Set allLocalAddresses = sch.getAllLocalAddresses(); + final Set addresses = new LinkedHashSet(allLocalAddresses.size()); + for (SocketAddress socketAddress : allLocalAddresses) { + addresses.add((InetSocketAddress) socketAddress); + } + return addresses; + } catch (Throwable ignored) { + return Collections.emptySet(); + } + } + + @Override + public boolean isActive() { + return isOpen() && localAddress0() != null; + } + + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + sch.bind(localAddress, config.getBacklog()); + } + + @Override + protected void doClose() throws Exception { + try { + selector.close(); + } catch (IOException e) { + logger.warn("Failed to close a selector.", e); + } + sch.close(); + } + + @Override + protected int doReadMessages(List buf) throws Exception { + if (!isActive()) { + return -1; + } + + SctpChannel s = null; + int acceptedChannels = 0; + try { + final int selectedKeys = selector.select(SO_TIMEOUT); + if (selectedKeys > 0) { + final Iterator selectionKeys = selector.selectedKeys().iterator(); + for (;;) { + SelectionKey key = selectionKeys.next(); + selectionKeys.remove(); + if (key.isAcceptable()) { + s = sch.accept(); + if (s != null) { + buf.add(new OioSctpChannel(this, s)); + acceptedChannels ++; + } + } + if (!selectionKeys.hasNext()) { + return acceptedChannels; + } + } + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted sctp channel.", t); + if (s != null) { + try { + s.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a sctp channel.", t2); + } + } + } + + return acceptedChannels; + } + + @Override + public ChannelFuture bindAddress(InetAddress localAddress) { + return bindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture bindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + sch.bindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + bindAddress(localAddress, promise); + } + }); + } + return promise; + } + + @Override + public ChannelFuture unbindAddress(InetAddress localAddress) { + return unbindAddress(localAddress, newPromise()); + } + + @Override + public ChannelFuture unbindAddress(final InetAddress localAddress, final ChannelPromise promise) { + if (eventLoop().inEventLoop()) { + try { + sch.unbindAddress(localAddress); + promise.setSuccess(); + } catch (Throwable t) { + promise.setFailure(t); + } + } else { + eventLoop().execute(new Runnable() { + @Override + public void run() { + unbindAddress(localAddress, promise); + } + }); + } + return promise; + } + + @Override + protected void doConnect( + SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected SocketAddress remoteAddress0() { + return null; + } + + @Override + protected void doDisconnect() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected Object filterOutboundMessage(Object msg) throws Exception { + throw new UnsupportedOperationException(); + } + + private final class OioSctpServerChannelConfig extends DefaultSctpServerChannelConfig { + private OioSctpServerChannelConfig(OioSctpServerChannel channel, SctpServerChannel javaChannel) { + super(channel, javaChannel); + } + + @Override + protected void autoReadCleared() { + clearReadPending(); + } + } +} diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/package-info.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/package-info.java new file mode 100644 index 0000000..6c90bc6 --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/oio/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Old blocking I/O based SCTP channel API implementation - recommended for + * a small number of connections (< 1000). + * + * @deprecated use NIO based SCTP implementation. + */ +package io.netty.channel.sctp.oio; diff --git a/netty-channel-sctp/src/main/java/io/netty/channel/sctp/package-info.java b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/package-info.java new file mode 100644 index 0000000..8e0c90b --- /dev/null +++ b/netty-channel-sctp/src/main/java/io/netty/channel/sctp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Abstract SCTP socket interfaces which extend the core channel API. + */ +package io.netty.channel.sctp; diff --git a/netty-channel-sctp/src/main/java/module-info.java b/netty-channel-sctp/src/main/java/module-info.java new file mode 100644 index 0000000..87c2a70 --- /dev/null +++ b/netty-channel-sctp/src/main/java/module-info.java @@ -0,0 +1,9 @@ +module org.xbib.io.netty.channel.sctp { + exports io.netty.channel.sctp; + exports io.netty.channel.sctp.nio; + exports io.netty.channel.sctp.oio; + requires org.xbib.io.netty.buffer; + requires org.xbib.io.netty.channel; + requires org.xbib.io.netty.util; + requires jdk.sctp; +} diff --git a/netty-handler-codec-sctp/build.gradle b/netty-handler-codec-sctp/build.gradle new file mode 100644 index 0000000..105c9b1 --- /dev/null +++ b/netty-handler-codec-sctp/build.gradle @@ -0,0 +1,4 @@ +dependencies { + api project(':netty-channel-sctp') + api project(':netty-handler-codec') +} diff --git a/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpInboundByteStreamHandler.java b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpInboundByteStreamHandler.java new file mode 100644 index 0000000..c5503e4 --- /dev/null +++ b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpInboundByteStreamHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +package io.netty.handler.codec.sctp; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.sctp.SctpMessage; +import io.netty.handler.codec.CodecException; +import io.netty.handler.codec.MessageToMessageDecoder; + +import java.util.List; + +/** + * A ChannelHandler which receives {@link SctpMessage}s which belong to a application protocol form a specific + * SCTP Stream and decode it as {@link ByteBuf}. + */ +public class SctpInboundByteStreamHandler extends MessageToMessageDecoder { + private final int protocolIdentifier; + private final int streamIdentifier; + + /** + * @param streamIdentifier accepted stream number, this should be >=0 or <= max stream number of the association. + * @param protocolIdentifier supported application protocol. + */ + public SctpInboundByteStreamHandler(int protocolIdentifier, int streamIdentifier) { + this.protocolIdentifier = protocolIdentifier; + this.streamIdentifier = streamIdentifier; + } + + @Override + public final boolean acceptInboundMessage(Object msg) throws Exception { + if (super.acceptInboundMessage(msg)) { + return acceptInboundMessage((SctpMessage) msg); + } + return false; + } + + protected boolean acceptInboundMessage(SctpMessage msg) { + return msg.protocolIdentifier() == protocolIdentifier && msg.streamIdentifier() == streamIdentifier; + } + + @Override + protected void decode(ChannelHandlerContext ctx, SctpMessage msg, List out) throws Exception { + if (!msg.isComplete()) { + throw new CodecException(String.format("Received SctpMessage is not complete, please add %s in the " + + "pipeline before this handler", SctpMessageCompletionHandler.class.getSimpleName())); + } + out.add(msg.content().retain()); + } +} diff --git a/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java new file mode 100644 index 0000000..13fe290 --- /dev/null +++ b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +package io.netty.handler.codec.sctp; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.sctp.SctpMessage; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; + +import java.util.List; + +/** + * {@link MessageToMessageDecoder} which will take care of handle fragmented {@link SctpMessage}s, so + * only complete {@link SctpMessage}s will be forwarded to the next + * {@link ChannelInboundHandler}. + */ +public class SctpMessageCompletionHandler extends MessageToMessageDecoder { + private final IntObjectMap fragments = new IntObjectHashMap(); + + @Override + protected void decode(ChannelHandlerContext ctx, SctpMessage msg, List out) throws Exception { + final ByteBuf byteBuf = msg.content(); + final int protocolIdentifier = msg.protocolIdentifier(); + final int streamIdentifier = msg.streamIdentifier(); + final boolean isComplete = msg.isComplete(); + final boolean isUnordered = msg.isUnordered(); + + ByteBuf frag = fragments.remove(streamIdentifier); + if (frag == null) { + frag = Unpooled.EMPTY_BUFFER; + } + + if (isComplete && !frag.isReadable()) { + //data chunk is not fragmented + out.add(msg); + } else if (!isComplete && frag.isReadable()) { + //more message to complete + fragments.put(streamIdentifier, Unpooled.wrappedBuffer(frag, byteBuf)); + } else if (isComplete && frag.isReadable()) { + //last message to complete + SctpMessage assembledMsg = new SctpMessage( + protocolIdentifier, + streamIdentifier, + isUnordered, + Unpooled.wrappedBuffer(frag, byteBuf)); + out.add(assembledMsg); + } else { + //first incomplete message + fragments.put(streamIdentifier, byteBuf); + } + byteBuf.retain(); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + for (ByteBuf buffer: fragments.values()) { + buffer.release(); + } + fragments.clear(); + super.handlerRemoved(ctx); + } +} diff --git a/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageToMessageDecoder.java b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageToMessageDecoder.java new file mode 100644 index 0000000..483e6df --- /dev/null +++ b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpMessageToMessageDecoder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +package io.netty.handler.codec.sctp; + +import io.netty.channel.sctp.SctpMessage; +import io.netty.handler.codec.CodecException; +import io.netty.handler.codec.MessageToMessageDecoder; + +public abstract class SctpMessageToMessageDecoder extends MessageToMessageDecoder { + + @Override + public boolean acceptInboundMessage(Object msg) throws Exception { + if (msg instanceof SctpMessage) { + SctpMessage sctpMsg = (SctpMessage) msg; + if (sctpMsg.isComplete()) { + return true; + } + + throw new CodecException(String.format("Received SctpMessage is not complete, please add %s in " + + "the pipeline before this handler", SctpMessageCompletionHandler.class.getSimpleName())); + } else { + return false; + } + } +} diff --git a/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpOutboundByteStreamHandler.java b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpOutboundByteStreamHandler.java new file mode 100644 index 0000000..9a5d4e1 --- /dev/null +++ b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/SctpOutboundByteStreamHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.handler.codec.sctp; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.sctp.SctpMessage; +import io.netty.handler.codec.MessageToMessageEncoder; + +import java.util.List; + +/** + * A ChannelHandler which transform {@link ByteBuf} to {@link SctpMessage} and send it through a specific stream + * with given protocol identifier. + * Unordered delivery of all messages may be requested by passing unordered = true to the constructor. + */ +public class SctpOutboundByteStreamHandler extends MessageToMessageEncoder { + private final int streamIdentifier; + private final int protocolIdentifier; + private final boolean unordered; + + /** + * @param streamIdentifier stream number, this should be >=0 or <= max stream number of the association. + * @param protocolIdentifier supported application protocol id. + */ + public SctpOutboundByteStreamHandler(int streamIdentifier, int protocolIdentifier) { + this(streamIdentifier, protocolIdentifier, false); + } + + /** + * @param streamIdentifier stream number, this should be >=0 or <= max stream number of the association. + * @param protocolIdentifier supported application protocol id. + * @param unordered if {@literal true}, SCTP Data Chunks will be sent with the U (unordered) flag set. + */ + public SctpOutboundByteStreamHandler(int streamIdentifier, int protocolIdentifier, boolean unordered) { + this.streamIdentifier = streamIdentifier; + this.protocolIdentifier = protocolIdentifier; + this.unordered = unordered; + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { + out.add(new SctpMessage(protocolIdentifier, streamIdentifier, unordered, msg.retain())); + } +} diff --git a/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/package-info.java b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/package-info.java new file mode 100644 index 0000000..be8e383 --- /dev/null +++ b/netty-handler-codec-sctp/src/main/java/io/netty/handler/codec/sctp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Decoder and encoders to manage message completion and multi-streaming codec in SCTP/IP. + */ +package io.netty.handler.codec.sctp; diff --git a/netty-handler-codec-sctp/src/main/java/module-info.java b/netty-handler-codec-sctp/src/main/java/module-info.java new file mode 100644 index 0000000..b7865cb --- /dev/null +++ b/netty-handler-codec-sctp/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module org.xbib.io.netty.handler.codec.sctp { + exports io.netty.handler.codec.sctp; + requires org.xbib.io.netty.buffer; + requires org.xbib.io.netty.channel; + requires org.xbib.io.netty.channel.sctp; + requires org.xbib.io.netty.handler.codec; + requires org.xbib.io.netty.util; +} diff --git a/netty-testsuite/build.gradle b/netty-testsuite/build.gradle new file mode 100644 index 0000000..ee173f4 --- /dev/null +++ b/netty-testsuite/build.gradle @@ -0,0 +1,18 @@ +dependencies { + api project(':netty-buffer') + api project(':netty-channel') + api project(':netty-util') + implementation project(':netty-channel-sctp') + implementation project(':netty-handler-codec-sctp') + implementation project(':netty-handler-codec-spdy') + implementation project(':netty-handler') + implementation project(':netty-handler-ssl') + implementation testLibs.xz.tools + implementation testLibs.assertj + implementation testLibs.junit.jupiter.api + implementation testLibs.junit.jupiter.params + implementation testLibs.hamcrest + runtimeOnly testLibs.junit.jupiter.engine + runtimeOnly testLibs.junit.vintage.engine + runtimeOnly testLibs.junit.jupiter.platform.launcher +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractComboTestsuiteTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractComboTestsuiteTest.java new file mode 100644 index 0000000..16318ed --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractComboTestsuiteTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport; + +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.util.TestUtils; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.TestInfo; + +import java.util.List; + +public abstract class AbstractComboTestsuiteTest, + CB extends AbstractBootstrap> { + protected final InternalLogger logger = InternalLoggerFactory.getInstance(getClass()); + protected volatile CB cb; + protected volatile SB sb; + + protected abstract List> newFactories(); + + protected List newAllocators() { + return TestsuitePermutation.allocator(); + } + + protected void run(TestInfo testInfo, Runner runner) throws Throwable { + List> combos = newFactories(); + String methodName = TestUtils.testMethodName(testInfo); + for (ByteBufAllocator allocator: newAllocators()) { + int i = 0; + for (TestsuitePermutation.BootstrapComboFactory e: combos) { + sb = e.newServerInstance(); + cb = e.newClientInstance(); + configure(sb, cb, allocator); + logger.info(String.format( + "Running: %s %d of %d (%s + %s) with %s", + methodName, ++ i, combos.size(), sb, cb, StringUtil.simpleClassName(allocator))); + runner.run(sb, cb); + } + } + } + + protected abstract void configure(SB sb, CB cb, ByteBufAllocator allocator); + + public interface Runner, CB extends AbstractBootstrap> { + void run(SB sb, CB cb) throws Throwable; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractSingleThreadEventLoopTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractSingleThreadEventLoopTest.java new file mode 100644 index 0000000..27da4e3 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractSingleThreadEventLoopTest.java @@ -0,0 +1,316 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import io.netty.channel.SingleThreadEventLoop; +import io.netty.channel.local.LocalAddress; +import io.netty.channel.local.LocalServerChannel; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.function.Executable; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public abstract class AbstractSingleThreadEventLoopTest { + + @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) + public void testChannelsRegistered() throws Exception { + EventLoopGroup group = newEventLoopGroup(); + final SingleThreadEventLoop loop = (SingleThreadEventLoop) group.next(); + + try { + final Channel ch1 = newChannel(); + final Channel ch2 = newChannel(); + + int rc = registeredChannels(loop); + boolean channelCountSupported = rc != -1; + + if (channelCountSupported) { + assertEquals(0, registeredChannels(loop)); + } + + assertTrue(loop.register(ch1).syncUninterruptibly().isSuccess()); + assertTrue(loop.register(ch2).syncUninterruptibly().isSuccess()); + if (channelCountSupported) { + checkNumRegisteredChannels(loop, 2); + } + + assertTrue(ch1.deregister().syncUninterruptibly().isSuccess()); + if (channelCountSupported) { + checkNumRegisteredChannels(loop, 1); + } + } finally { + group.shutdownGracefully(); + } + } + + private static void checkNumRegisteredChannels(SingleThreadEventLoop loop, int numChannels) throws Exception { + // We need to loop as some EventLoop implementations may need some time to update the counter correctly. + while (registeredChannels(loop) != numChannels) { + Thread.sleep(50); + } + } + + // Only reliable if run from event loop + private static int registeredChannels(final SingleThreadEventLoop loop) throws Exception { + return loop.submit(new Callable() { + @Override + public Integer call() { + return loop.registeredChannels(); + } + }).get(1, TimeUnit.SECONDS); + } + + @Test + @SuppressWarnings("deprecation") + public void shutdownBeforeStart() throws Exception { + EventLoopGroup group = newEventLoopGroup(); + assertFalse(group.awaitTermination(2, TimeUnit.MILLISECONDS)); + group.shutdown(); + assertTrue(group.awaitTermination(200, TimeUnit.MILLISECONDS)); + } + + @Test + public void shutdownGracefullyZeroQuietBeforeStart() throws Exception { + EventLoopGroup group = newEventLoopGroup(); + assertTrue(group.shutdownGracefully(0L, 2L, TimeUnit.SECONDS).await(200L)); + } + + // Copied from AbstractEventLoopTest + @Test + @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS) + public void testShutdownGracefullyNoQuietPeriod() throws Exception { + EventLoopGroup loop = newEventLoopGroup(); + ServerBootstrap b = new ServerBootstrap(); + b.group(loop) + .channel(serverChannelClass()) + .childHandler(new ChannelInboundHandlerAdapter()); + + // Not close the Channel to ensure the EventLoop is still shutdown in time. + ChannelFuture cf = serverChannelClass() == LocalServerChannel.class + ? b.bind(new LocalAddress("local")) : b.bind(0); + cf.sync().channel(); + + Future f = loop.shutdownGracefully(0, 1, TimeUnit.MINUTES); + assertTrue(loop.awaitTermination(600, TimeUnit.MILLISECONDS)); + assertTrue(f.syncUninterruptibly().isSuccess()); + assertTrue(loop.isShutdown()); + assertTrue(loop.isTerminated()); + } + + @Test + public void shutdownGracefullyBeforeStart() throws Exception { + EventLoopGroup group = newEventLoopGroup(); + assertTrue(group.shutdownGracefully(200L, 1000L, TimeUnit.MILLISECONDS).await(500L)); + } + + @Test + public void gracefulShutdownAfterStart() throws Exception { + EventLoop loop = newEventLoopGroup().next(); + final CountDownLatch latch = new CountDownLatch(1); + loop.execute(new Runnable() { + @Override + public void run() { + latch.countDown(); + } + }); + + // Wait for the event loop thread to start. + latch.await(); + + // Request the event loop thread to stop. + loop.shutdownGracefully(200L, 3000L, TimeUnit.MILLISECONDS); + + // Wait until the event loop is terminated. + assertTrue(loop.awaitTermination(500L, TimeUnit.MILLISECONDS)); + + assertRejection(loop); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testChannelsIteratorEmpty() throws Exception { + assumeTrue(supportsChannelIteration()); + EventLoopGroup group = newEventLoopGroup(); + final SingleThreadEventLoop loop = (SingleThreadEventLoop) group.next(); + try { + runBlockingOn(loop, new Runnable() { + @Override + public void run() { + final Iterator iterator = loop.registeredChannelsIterator(); + + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, new Executable() { + @Override + public void execute() { + iterator.next(); + } + }); + } + }); + } finally { + group.shutdownGracefully(); + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testChannelsIterator() throws Exception { + assumeTrue(supportsChannelIteration()); + EventLoopGroup group = newEventLoopGroup(); + final SingleThreadEventLoop loop = (SingleThreadEventLoop) group.next(); + try { + final Channel ch1 = newChannel(); + final Channel ch2 = newChannel(); + loop.register(ch1).syncUninterruptibly(); + loop.register(ch2).syncUninterruptibly(); + assertEquals(2, registeredChannels(loop)); + + runBlockingOn(loop, new Runnable() { + @Override + public void run() { + final Iterator iterator = loop.registeredChannelsIterator(); + + assertTrue(iterator.hasNext()); + Channel actualCh1 = iterator.next(); + assertNotNull(actualCh1); + + assertTrue(iterator.hasNext()); + Channel actualCh2 = iterator.next(); + assertNotNull(actualCh2); + + Set expected = new HashSet(4); + expected.add(ch1); + expected.add(ch2); + expected.remove(actualCh1); + expected.remove(actualCh2); + assertTrue(expected.isEmpty()); + + assertFalse(iterator.hasNext()); + assertThrows(NoSuchElementException.class, new Executable() { + @Override + public void execute() { + iterator.next(); + } + }); + } + }); + } finally { + group.shutdownGracefully(); + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testChannelsIteratorRemoveThrows() throws Exception { + assumeTrue(supportsChannelIteration()); + EventLoopGroup group = newEventLoopGroup(); + final SingleThreadEventLoop loop = (SingleThreadEventLoop) group.next(); + + try { + final Channel ch = newChannel(); + loop.register(ch).syncUninterruptibly(); + assertEquals(1, registeredChannels(loop)); + + runBlockingOn(loop, new Runnable() { + @Override + public void run() { + assertThrows(UnsupportedOperationException.class, new Executable() { + @Override + public void execute() { + loop.registeredChannelsIterator().remove(); + } + }); + } + }); + } finally { + group.shutdownGracefully(); + } + } + + private static void runBlockingOn(EventLoop eventLoop, final Runnable action) { + final Promise promise = eventLoop.newPromise(); + eventLoop.execute(new Runnable() { + @Override + public void run() { + try { + action.run(); + promise.setSuccess(null); + } catch (Throwable t) { + promise.tryFailure(t); + } + } + }); + try { + promise.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + Throwable cause = promise.cause(); + if (cause != null) { + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + throw new RuntimeException(cause); + } + } + + private static final Runnable NOOP = new Runnable() { + @Override + public void run() { } + }; + + private static void assertRejection(EventExecutor loop) { + try { + loop.execute(NOOP); + fail("A task must be rejected after shutdown() is called."); + } catch (RejectedExecutionException e) { + // Expected + } + } + + protected boolean supportsChannelIteration() { + return false; + } + protected abstract EventLoopGroup newEventLoopGroup(); + protected abstract Channel newChannel(); + protected abstract Class serverChannelClass(); +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractTestsuiteTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractTestsuiteTest.java new file mode 100644 index 0000000..9f912b7 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/AbstractTestsuiteTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport; + +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.testsuite.util.TestUtils; +import io.netty.util.internal.StringUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.TestInfo; + +import java.util.List; + +public abstract class AbstractTestsuiteTest> { + protected final InternalLogger logger = InternalLoggerFactory.getInstance(getClass()); + protected volatile T cb; + + protected abstract List> newFactories(); + + protected List newAllocators() { + return TestsuitePermutation.allocator(); + } + + protected void run(TestInfo testInfo, Runner runner) throws Throwable { + List> combos = newFactories(); + String methodName = TestUtils.testMethodName(testInfo); + for (ByteBufAllocator allocator: newAllocators()) { + int i = 0; + for (TestsuitePermutation.BootstrapFactory e: combos) { + cb = e.newInstance(); + configure(cb, allocator); + logger.info(String.format( + "Running: %s %d of %d with %s", + methodName, ++ i, combos.size(), StringUtil.simpleClassName(allocator))); + runner.run(cb); + } + } + } + + protected abstract void configure(T bootstrap, ByteBufAllocator allocator); + + public interface Runner> { + void run(CB cb) throws Throwable; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/DefaultEventLoopTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/DefaultEventLoopTest.java new file mode 100644 index 0000000..36dceca --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/DefaultEventLoopTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport; + +import io.netty.channel.Channel; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import io.netty.channel.SingleThreadEventLoop; +import io.netty.channel.local.LocalChannel; +import io.netty.channel.local.LocalServerChannel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.function.Executable; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DefaultEventLoopTest extends AbstractSingleThreadEventLoopTest { + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testChannelsIteratorNotSupported() throws Exception { + EventLoopGroup group = newEventLoopGroup(); + final SingleThreadEventLoop loop = (SingleThreadEventLoop) group.next(); + try { + final Channel ch = newChannel(); + loop.register(ch).syncUninterruptibly(); + + assertThrows(UnsupportedOperationException.class, new Executable() { + @Override + public void execute() throws Throwable { + loop.registeredChannelsIterator(); + } + }); + } finally { + group.shutdownGracefully(); + } + } + + @Override + protected EventLoopGroup newEventLoopGroup() { + return new DefaultEventLoopGroup(); + } + + @Override + protected Channel newChannel() { + return new LocalChannel(); + } + + @Override + protected Class serverChannelClass() { + return LocalServerChannel.class; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/NioEventLoopTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/NioEventLoopTest.java new file mode 100644 index 0000000..90e9649 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/NioEventLoopTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport; + +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +public class NioEventLoopTest extends AbstractSingleThreadEventLoopTest { + + @Override + protected boolean supportsChannelIteration() { + return true; + } + + @Override + protected EventLoopGroup newEventLoopGroup() { + return new NioEventLoopGroup(); + } + + @Override + protected Channel newChannel() { + return new NioSocketChannel(); + } + + @Override + protected Class serverChannelClass() { + return NioServerSocketChannel.class; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/TestsuitePermutation.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/TestsuitePermutation.java new file mode 100644 index 0000000..7f2a2f7 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/TestsuitePermutation.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport; + +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.UnpooledByteBufAllocator; + +import java.util.ArrayList; +import java.util.List; + +public final class TestsuitePermutation { + + public static List allocator() { + List allocators = new ArrayList(); + allocators.add(UnpooledByteBufAllocator.DEFAULT); + allocators.add(PooledByteBufAllocator.DEFAULT); + return allocators; + } + + private TestsuitePermutation() { } + + public interface BootstrapFactory> { + CB newInstance(); + } + + public interface BootstrapComboFactory, CB extends AbstractBootstrap> { + SB newServerInstance(); + CB newClientInstance(); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/package-info.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/package-info.java new file mode 100644 index 0000000..c135c96 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Common test suite classes. + */ +package io.netty.testsuite.transport; diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/AbstractSctpTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/AbstractSctpTest.java new file mode 100644 index 0000000..4bde9a1 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/AbstractSctpTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.sctp; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.testsuite.transport.AbstractComboTestsuiteTest; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; + +import java.net.InetSocketAddress; +import java.util.List; + +public abstract class AbstractSctpTest extends AbstractComboTestsuiteTest { + @Override + protected List> newFactories() { + return SctpTestPermutation.sctpChannel(); + } + + @Override + protected void configure(ServerBootstrap serverBootstrap, Bootstrap bootstrap, ByteBufAllocator allocator) { + serverBootstrap.localAddress(new InetSocketAddress(NetUtil.LOCALHOST, 0)); + serverBootstrap.option(ChannelOption.ALLOCATOR, allocator); + serverBootstrap.childOption(ChannelOption.ALLOCATOR, allocator); + bootstrap.option(ChannelOption.ALLOCATOR, allocator); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java new file mode 100644 index 0000000..bc42ea2 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpEchoTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.sctp; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.sctp.SctpChannel; +import io.netty.handler.codec.sctp.SctpInboundByteStreamHandler; +import io.netty.handler.codec.sctp.SctpMessageCompletionHandler; +import io.netty.handler.codec.sctp.SctpOutboundByteStreamHandler; +import io.netty.testsuite.util.TestUtils; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.TestInfo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class SctpEchoTest extends AbstractSctpTest { + + private static final Random random = new Random(); + static final byte[] data = new byte[4096]; //could not test ultra jumbo frames + + static { + random.nextBytes(data); + } + + @Test + public void testSimpleEcho(TestInfo testInfo) throws Throwable { + assumeTrue(TestUtils.isSctpSupported()); + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSimpleEcho(serverBootstrap, bootstrap); + } + }); + } + + public void testSimpleEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, false); + } + + @Test + public void testSimpleEchoUnordered(TestInfo testInfo) throws Throwable { + assumeTrue(TestUtils.isSctpSupported()); + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSimpleEchoUnordered(serverBootstrap, bootstrap); + } + }); + } + + public void testSimpleEchoUnordered(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, true); + } + + private static void testSimpleEcho0(ServerBootstrap sb, Bootstrap cb, final boolean unordered) throws Throwable { + final EchoHandler sh = new EchoHandler(); + final EchoHandler ch = new EchoHandler(); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(SctpChannel c) throws Exception { + c.pipeline().addLast( + new SctpMessageCompletionHandler(), + new SctpInboundByteStreamHandler(0, 0), + new SctpOutboundByteStreamHandler(0, 0, unordered), + sh); + } + }); + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(SctpChannel c) throws Exception { + c.pipeline().addLast( + new SctpMessageCompletionHandler(), + new SctpInboundByteStreamHandler(0, 0), + new SctpOutboundByteStreamHandler(0, 0, unordered), + ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + cc.writeAndFlush(Unpooled.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelInboundHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.parent() != null) { + channel.writeAndFlush(Unpooled.wrappedBuffer(actual)); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpTestPermutation.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpTestPermutation.java new file mode 100644 index 0000000..7fb3508 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/SctpTestPermutation.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.sctp; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.oio.OioEventLoopGroup; +import io.netty.channel.sctp.nio.NioSctpChannel; +import io.netty.channel.sctp.nio.NioSctpServerChannel; +import io.netty.channel.sctp.oio.OioSctpChannel; +import io.netty.channel.sctp.oio.OioSctpServerChannel; +import io.netty.testsuite.util.TestUtils; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; +import io.netty.util.concurrent.DefaultThreadFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class SctpTestPermutation { + + private static final int BOSSES = 2; + private static final int WORKERS = 3; + private static final EventLoopGroup nioBossGroup = + new NioEventLoopGroup(BOSSES, new DefaultThreadFactory("testsuite-sctp-nio-boss", true)); + private static final EventLoopGroup nioWorkerGroup = + new NioEventLoopGroup(WORKERS, new DefaultThreadFactory("testsuite-sctp-nio-worker", true)); + private static final EventLoopGroup oioBossGroup = + new OioEventLoopGroup(Integer.MAX_VALUE, new DefaultThreadFactory("testsuite-sctp-oio-boss", true)); + private static final EventLoopGroup oioWorkerGroup = + new OioEventLoopGroup(Integer.MAX_VALUE, new DefaultThreadFactory("testsuite-sctp-oio-worker", true)); + + static List> sctpServerChannel() { + if (!TestUtils.isSctpSupported()) { + return Collections.emptyList(); + } + + List> list = new ArrayList>(); + // Make the list of ServerBootstrap factories. + list.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap(). + group(nioBossGroup, nioWorkerGroup). + channel(NioSctpServerChannel.class); + } + }); + list.add(new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap(). + group(oioBossGroup, oioWorkerGroup). + channel(OioSctpServerChannel.class); + } + }); + + return list; + } + + static List> sctpClientChannel() { + if (!TestUtils.isSctpSupported()) { + return Collections.emptyList(); + } + + List> list = new ArrayList>(); + list.add(new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channel(NioSctpChannel.class); + } + }); + list.add(new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(oioWorkerGroup).channel(OioSctpChannel.class); + } + }); + return list; + } + + static List> sctpChannel() { + List> list = + new ArrayList>(); + + // Make the list of SCTP ServerBootstrap factories. + List> sbfs = sctpServerChannel(); + + // Make the list of SCTP Bootstrap factories. + List> cbfs = sctpClientChannel(); + + // Populate the combinations + for (BootstrapFactory sbf: sbfs) { + for (BootstrapFactory cbf: cbfs) { + final BootstrapFactory sbf0 = sbf; + final BootstrapFactory cbf0 = cbf; + list.add(new BootstrapComboFactory() { + @Override + public ServerBootstrap newServerInstance() { + return sbf0.newInstance(); + } + + @Override + public Bootstrap newClientInstance() { + return cbf0.newInstance(); + } + }); + } + } + + return list; + } + + private SctpTestPermutation() { } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/package-info.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/package-info.java new file mode 100644 index 0000000..95fdbf8 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/sctp/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Test suite classes for sctp transport + */ +package io.netty.testsuite.transport.sctp; diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractClientSocketTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractClientSocketTest.java new file mode 100644 index 0000000..765e96f --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractClientSocketTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.testsuite.transport.AbstractTestsuiteTest; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +public abstract class AbstractClientSocketTest extends AbstractTestsuiteTest { + @Override + protected List> newFactories() { + return SocketTestPermutation.INSTANCE.clientSocket(); + } + + @Override + protected void configure(Bootstrap bootstrap, ByteBufAllocator allocator) { + bootstrap.option(ChannelOption.ALLOCATOR, allocator); + } + + protected SocketAddress newSocketAddress() { + return new InetSocketAddress(NetUtil.LOCALHOST, 0); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java new file mode 100644 index 0000000..319242f --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractDatagramTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.testsuite.transport.AbstractComboTestsuiteTest; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +public abstract class AbstractDatagramTest extends AbstractComboTestsuiteTest { + @Override + protected List> newFactories() { + return SocketTestPermutation.INSTANCE.datagram(socketInternetProtocalFamily()); + } + + @Override + protected void configure(Bootstrap bootstrap, Bootstrap bootstrap2, ByteBufAllocator allocator) { + bootstrap.option(ChannelOption.ALLOCATOR, allocator); + bootstrap2.option(ChannelOption.ALLOCATOR, allocator); + } + + protected SocketAddress newSocketAddress() { + switch (socketInternetProtocalFamily()) { + case IPv4: + return new InetSocketAddress(NetUtil.LOCALHOST4, 0); + case IPv6: + return new InetSocketAddress(NetUtil.LOCALHOST6, 0); + default: + throw new AssertionError(); + } + } + + protected InternetProtocolFamily internetProtocolFamily() { + return InternetProtocolFamily.IPv4; + } + + protected InternetProtocolFamily groupInternetProtocalFamily() { + return internetProtocolFamily(); + } + + protected InternetProtocolFamily socketInternetProtocalFamily() { + return internetProtocolFamily(); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractServerSocketTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractServerSocketTest.java new file mode 100644 index 0000000..0233046 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractServerSocketTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.testsuite.transport.AbstractTestsuiteTest; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +public abstract class AbstractServerSocketTest extends AbstractTestsuiteTest { + + @Override + protected List> newFactories() { + return SocketTestPermutation.INSTANCE.serverSocket(); + } + + @Override + protected void configure(ServerBootstrap bootstrap, ByteBufAllocator allocator) { + bootstrap.localAddress(newSocketAddress()); + bootstrap.option(ChannelOption.ALLOCATOR, allocator); + bootstrap.childOption(ChannelOption.ALLOCATOR, allocator); + } + + protected SocketAddress newSocketAddress() { + return new InetSocketAddress( + NetUtil.LOCALHOST, 0); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketReuseFdTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketReuseFdTest.java new file mode 100644 index 0000000..abd7261 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketReuseFdTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.CharsetUtil; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.net.SocketAddress; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public abstract class AbstractSocketReuseFdTest extends AbstractSocketTest { + @Override + protected abstract SocketAddress newSocketAddress(); + + @Override + protected abstract List> newFactories(); + + @Test + @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + public void testReuseFd(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testReuseFd(serverBootstrap, bootstrap); + } + }); + } + + public void testReuseFd(ServerBootstrap sb, Bootstrap cb) throws Throwable { + sb.childOption(ChannelOption.AUTO_READ, true); + cb.option(ChannelOption.AUTO_READ, true); + + // Use a number which will typically not exceed /proc/sys/net/core/somaxconn (which is 128 on linux by default + // often). + int numChannels = 100; + final AtomicReference globalException = new AtomicReference(); + final AtomicInteger serverRemaining = new AtomicInteger(numChannels); + final AtomicInteger clientRemaining = new AtomicInteger(numChannels); + final Promise serverDonePromise = ImmediateEventExecutor.INSTANCE.newPromise(); + final Promise clientDonePromise = ImmediateEventExecutor.INSTANCE.newPromise(); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) { + ReuseFdHandler sh = new ReuseFdHandler( + false, + globalException, + serverRemaining, + serverDonePromise); + sch.pipeline().addLast("handler", sh); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) { + ReuseFdHandler ch = new ReuseFdHandler( + true, + globalException, + clientRemaining, + clientDonePromise); + sch.pipeline().addLast("handler", ch); + } + }); + + ChannelFutureListener listener = new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (!future.isSuccess()) { + clientDonePromise.tryFailure(future.cause()); + } + } + }; + + Channel sc = sb.bind().sync().channel(); + for (int i = 0; i < numChannels; i++) { + cb.connect(sc.localAddress()).addListener(listener); + } + + clientDonePromise.sync(); + serverDonePromise.sync(); + sc.close().sync(); + + if (globalException.get() != null && !(globalException.get() instanceof IOException)) { + throw globalException.get(); + } + } + + static class ReuseFdHandler extends ChannelInboundHandlerAdapter { + private static final String EXPECTED_PAYLOAD = "payload"; + + private final Promise donePromise; + private final AtomicInteger remaining; + private final boolean client; + volatile Channel channel; + final AtomicReference globalException; + final AtomicReference exception = new AtomicReference(); + final StringBuilder received = new StringBuilder(); + + ReuseFdHandler( + boolean client, + AtomicReference globalException, + AtomicInteger remaining, + Promise donePromise) { + this.client = client; + this.globalException = globalException; + this.remaining = remaining; + this.donePromise = donePromise; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + channel = ctx.channel(); + if (client) { + ctx.writeAndFlush(Unpooled.copiedBuffer(EXPECTED_PAYLOAD, CharsetUtil.US_ASCII)); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) msg; + received.append(buf.toString(CharsetUtil.US_ASCII)); + buf.release(); + + if (received.toString().equals(EXPECTED_PAYLOAD)) { + if (client) { + ctx.close(); + } else { + ctx.writeAndFlush(Unpooled.copiedBuffer(EXPECTED_PAYLOAD, CharsetUtil.US_ASCII)); + } + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + if (exception.compareAndSet(null, cause)) { + donePromise.tryFailure(new IllegalStateException("exceptionCaught: " + ctx.channel(), cause)); + ctx.close(); + } + globalException.compareAndSet(null, cause); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + if (remaining.decrementAndGet() == 0) { + if (received.toString().equals(EXPECTED_PAYLOAD)) { + donePromise.setSuccess(null); + } else { + donePromise.tryFailure(new Exception("Unexpected payload:" + received)); + } + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketShutdownOutputByPeerTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketShutdownOutputByPeerTest.java new file mode 100644 index 0000000..e3d2f67 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketShutdownOutputByPeerTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.ChannelInputShutdownEvent; +import io.netty.channel.socket.DuplexChannel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.net.SocketAddress; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public abstract class AbstractSocketShutdownOutputByPeerTest extends AbstractServerSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testShutdownOutput(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap) throws Throwable { + testShutdownOutput(serverBootstrap); + } + }); + } + + public void testShutdownOutput(ServerBootstrap sb) throws Throwable { + TestHandler h = new TestHandler(); + Socket s = newSocket(); + Channel sc = null; + try { + sc = sb.childHandler(h).childOption(ChannelOption.ALLOW_HALF_CLOSURE, true).bind().sync().channel(); + + connect(s, sc.localAddress()); + write(s, 1); + + assertEquals(1, (int) h.queue.take()); + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertFalse(h.ch.isInputShutdown()); + assertFalse(h.ch.isOutputShutdown()); + + shutdownOutput(s); + + h.halfClosure.await(); + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertTrue(h.ch.isInputShutdown()); + assertFalse(h.ch.isOutputShutdown()); + + while (h.closure.getCount() != 1 && h.halfClosureCount.intValue() != 1) { + Thread.sleep(100); + } + } finally { + if (sc != null) { + sc.close(); + } + close(s); + } + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testShutdownOutputWithoutOption(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap) throws Throwable { + testShutdownOutputWithoutOption(serverBootstrap); + } + }); + } + + public void testShutdownOutputWithoutOption(ServerBootstrap sb) throws Throwable { + TestHandler h = new TestHandler(); + Socket s = newSocket(); + Channel sc = null; + try { + sc = sb.childHandler(h).bind().sync().channel(); + + connect(s, sc.localAddress()); + write(s, 1); + + assertEquals(1, (int) h.queue.take()); + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertFalse(h.ch.isInputShutdown()); + assertFalse(h.ch.isOutputShutdown()); + + shutdownOutput(s); + + h.closure.await(); + + assertFalse(h.ch.isOpen()); + assertFalse(h.ch.isActive()); + assertTrue(h.ch.isInputShutdown()); + assertTrue(h.ch.isOutputShutdown()); + + while (h.halfClosure.getCount() != 1 && h.halfClosureCount.intValue() != 0) { + Thread.sleep(100); + } + } finally { + if (sc != null) { + sc.close(); + } + close(s); + } + } + + protected abstract void shutdownOutput(Socket s) throws IOException; + + protected abstract void connect(Socket s, SocketAddress address) throws IOException; + + protected abstract void close(Socket s) throws IOException; + + protected abstract void write(Socket s, int data) throws IOException; + + protected abstract Socket newSocket(); + + private static class TestHandler extends SimpleChannelInboundHandler { + volatile DuplexChannel ch; + final BlockingQueue queue = new LinkedBlockingQueue(); + final CountDownLatch halfClosure = new CountDownLatch(1); + final CountDownLatch closure = new CountDownLatch(1); + final AtomicInteger halfClosureCount = new AtomicInteger(); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ch = (DuplexChannel) ctx.channel(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + closure.countDown(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + queue.offer(msg.readByte()); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof ChannelInputShutdownEvent) { + halfClosureCount.incrementAndGet(); + halfClosure.countDown(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketTest.java new file mode 100644 index 0000000..2f32444 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/AbstractSocketTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelOption; +import io.netty.testsuite.transport.AbstractComboTestsuiteTest; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +public abstract class AbstractSocketTest extends AbstractComboTestsuiteTest { + @Override + protected List> newFactories() { + return SocketTestPermutation.INSTANCE.socket(); + } + + @Override + protected void configure(ServerBootstrap sb, Bootstrap cb, ByteBufAllocator allocator) { + sb.localAddress(newSocketAddress()); + sb.option(ChannelOption.ALLOCATOR, allocator); + sb.childOption(ChannelOption.ALLOCATOR, allocator); + cb.option(ChannelOption.ALLOCATOR, allocator); + } + + protected SocketAddress newSocketAddress() { + return new InetSocketAddress(NetUtil.LOCALHOST, 0); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/CompositeBufferGatheringWriteTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/CompositeBufferGatheringWriteTest.java new file mode 100644 index 0000000..67bac1a --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/CompositeBufferGatheringWriteTest.java @@ -0,0 +1,309 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +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.ChannelConfig; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.util.ReferenceCountUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CompositeBufferGatheringWriteTest extends AbstractSocketTest { + private static final int EXPECTED_BYTES = 20; + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testSingleCompositeBufferWrite(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSingleCompositeBufferWrite(serverBootstrap, bootstrap); + } + }); + } + + public void testSingleCompositeBufferWrite(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference clientReceived = new AtomicReference(); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.writeAndFlush(newCompositeBuffer(ctx.alloc())) + .addListener(ChannelFutureListener.CLOSE); + } + }); + } + }); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + private ByteBuf aggregator; + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + aggregator = ctx.alloc().buffer(EXPECTED_BYTES); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + try { + if (msg instanceof ByteBuf) { + aggregator.writeBytes((ByteBuf) msg); + } + } finally { + ReferenceCountUtil.release(msg); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // IOException is fine as it will also close the channel and may just be a connection reset. + if (!(cause instanceof IOException)) { + clientReceived.set(cause); + latch.countDown(); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + if (clientReceived.compareAndSet(null, aggregator)) { + try { + assertEquals(EXPECTED_BYTES, aggregator.readableBytes()); + } catch (Throwable cause) { + aggregator.release(); + aggregator = null; + clientReceived.set(cause); + } finally { + latch.countDown(); + } + } + } + }); + } + }); + + serverChannel = sb.bind().syncUninterruptibly().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + + ByteBuf expected = newCompositeBuffer(clientChannel.alloc()); + latch.await(); + Object received = clientReceived.get(); + if (received instanceof ByteBuf) { + ByteBuf actual = (ByteBuf) received; + assertEquals(expected, actual); + expected.release(); + actual.release(); + } else { + expected.release(); + throw (Throwable) received; + } + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testCompositeBufferPartialWriteDoesNotCorruptData(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testCompositeBufferPartialWriteDoesNotCorruptData(serverBootstrap, bootstrap); + } + }); + } + + protected void compositeBufferPartialWriteDoesNotCorruptDataInitServerConfig(ChannelConfig config, + int soSndBuf) { + } + + public void testCompositeBufferPartialWriteDoesNotCorruptData(ServerBootstrap sb, Bootstrap cb) throws Throwable { + // The scenario is the following: + // Limit SO_SNDBUF so that a single buffer can be written, and part of a CompositeByteBuf at the same time. + // We then write the single buffer, the CompositeByteBuf, and another single buffer and verify the data is not + // corrupted when we read it on the other side. + Channel serverChannel = null; + Channel clientChannel = null; + try { + Random r = new Random(); + final int soSndBuf = 1024; + ByteBufAllocator alloc = ByteBufAllocator.DEFAULT; + final ByteBuf expectedContent = alloc.buffer(soSndBuf * 2); + expectedContent.writeBytes(newRandomBytes(expectedContent.writableBytes(), r)); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference clientReceived = new AtomicReference(); + sb.childOption(ChannelOption.SO_SNDBUF, soSndBuf) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + compositeBufferPartialWriteDoesNotCorruptDataInitServerConfig(ctx.channel().config(), + soSndBuf); + // First single write + int offset = soSndBuf - 100; + ctx.write(expectedContent.retainedSlice(expectedContent.readerIndex(), offset)); + + // Build and write CompositeByteBuf + CompositeByteBuf compositeByteBuf = ctx.alloc().compositeBuffer(); + compositeByteBuf.addComponent(true, + expectedContent.retainedSlice(expectedContent.readerIndex() + offset, 50)); + offset += 50; + compositeByteBuf.addComponent(true, + expectedContent.retainedSlice(expectedContent.readerIndex() + offset, 200)); + offset += 200; + ctx.write(compositeByteBuf); + + // Write a single buffer that is smaller than the second component of the CompositeByteBuf + // above but small enough to fit in the remaining space allowed by the soSndBuf amount. + ctx.write(expectedContent.retainedSlice(expectedContent.readerIndex() + offset, 50)); + offset += 50; + + // Write the remainder of the content + ctx.writeAndFlush(expectedContent.retainedSlice(expectedContent.readerIndex() + offset, + expectedContent.readableBytes() - expectedContent.readerIndex() - offset)) + .addListener(ChannelFutureListener.CLOSE); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // IOException is fine as it will also close the channel and may just be a connection reset. + if (!(cause instanceof IOException)) { + clientReceived.set(cause); + latch.countDown(); + } + } + }); + } + }); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + private ByteBuf aggregator; + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + aggregator = ctx.alloc().buffer(expectedContent.readableBytes()); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + try { + if (msg instanceof ByteBuf) { + aggregator.writeBytes((ByteBuf) msg); + } + } finally { + ReferenceCountUtil.release(msg); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // IOException is fine as it will also close the channel and may just be a connection reset. + if (!(cause instanceof IOException)) { + clientReceived.set(cause); + latch.countDown(); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + if (clientReceived.compareAndSet(null, aggregator)) { + try { + assertEquals(expectedContent.readableBytes(), aggregator.readableBytes()); + } catch (Throwable cause) { + aggregator.release(); + aggregator = null; + clientReceived.set(cause); + } finally { + latch.countDown(); + } + } + } + }); + } + }); + + serverChannel = sb.bind().syncUninterruptibly().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + + latch.await(); + Object received = clientReceived.get(); + if (received instanceof ByteBuf) { + ByteBuf actual = (ByteBuf) received; + assertEquals(expectedContent, actual); + expectedContent.release(); + actual.release(); + } else { + expectedContent.release(); + throw (Throwable) received; + } + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + private static ByteBuf newCompositeBuffer(ByteBufAllocator alloc) { + CompositeByteBuf compositeByteBuf = alloc.compositeBuffer(); + compositeByteBuf.addComponent(true, alloc.directBuffer(4).writeInt(100)); + compositeByteBuf.addComponent(true, alloc.directBuffer(8).writeLong(123)); + compositeByteBuf.addComponent(true, alloc.directBuffer(8).writeLong(456)); + assertEquals(EXPECTED_BYTES, compositeByteBuf.readableBytes()); + return compositeByteBuf; + } + + private static byte[] newRandomBytes(int size, Random r) { + byte[] bytes = new byte[size]; + r.nextBytes(bytes); + return bytes; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramConnectNotExistsTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramConnectNotExistsTest.java new file mode 100644 index 0000000..4e8cc35 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramConnectNotExistsTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.oio.OioDatagramChannel; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.PlatformDependent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.net.PortUnreachableException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +public class DatagramConnectNotExistsTest extends AbstractClientSocketTest { + + @Override + protected List> newFactories() { + return SocketTestPermutation.INSTANCE.datagramSocket(); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testConnectNotExists(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) { + testConnectNotExists(bootstrap); + } + }); + } + + public void testConnectNotExists(Bootstrap cb) { + // Currently not works on windows + // See https://github.com/netty/netty/issues/11285 + assumeFalse(PlatformDependent.isWindows()); + final Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); + cb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + promise.trySuccess(cause); + } + }); + ChannelFuture future = cb.connect(NetUtil.LOCALHOST, SocketTestPermutation.BAD_PORT); + try { + Channel datagramChannel = future.syncUninterruptibly().channel(); + assertTrue(datagramChannel.isActive()); + datagramChannel.writeAndFlush( + Unpooled.copiedBuffer("test", CharsetUtil.US_ASCII)).syncUninterruptibly(); + if (!(datagramChannel instanceof OioDatagramChannel)) { + assertTrue(promise.syncUninterruptibly().getNow() instanceof PortUnreachableException); + } + } finally { + future.channel().close(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastIPv6Test.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastIPv6Test.java new file mode 100644 index 0000000..49b7c12 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastIPv6Test.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.util.internal.PlatformDependent; + +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +public class DatagramMulticastIPv6Test extends DatagramMulticastTest { + + @Override + public void testMulticast(Bootstrap sb, Bootstrap cb) throws Throwable { + // Not works on windows atm. + // See https://github.com/netty/netty/issues/11285 + assumeFalse(PlatformDependent.isWindows()); + super.testMulticast(sb, cb); + } + + @Override + protected InternetProtocolFamily internetProtocolFamily() { + return InternetProtocolFamily.IPv6; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java new file mode 100644 index 0000000..be1100b --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.socket.oio.OioDatagramChannel; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; +import io.netty.util.internal.SocketUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MulticastSocket; +import java.net.NetworkInterface; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class DatagramMulticastTest extends AbstractDatagramTest { + + @Test + public void testMulticast(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testMulticast(bootstrap, bootstrap2); + } + }); + } + + public void testMulticast(Bootstrap sb, Bootstrap cb) throws Throwable { + NetworkInterface iface = multicastNetworkInterface(); + assumeTrue(iface != null, "No NetworkInterface found that supports multicast and " + + socketInternetProtocalFamily()); + + MulticastTestHandler mhandler = new MulticastTestHandler(); + + sb.handler(new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + // Nothing will be sent. + } + }); + + cb.handler(mhandler); + + sb.option(ChannelOption.IP_MULTICAST_IF, iface); + sb.option(ChannelOption.SO_REUSEADDR, true); + + cb.option(ChannelOption.IP_MULTICAST_IF, iface); + cb.option(ChannelOption.SO_REUSEADDR, true); + + DatagramChannel sc = (DatagramChannel) sb.bind(newSocketAddress(iface)).sync().channel(); + assertEquals(iface, sc.config().getNetworkInterface()); + assertInterfaceAddress(iface, sc.config().getInterface()); + + InetSocketAddress addr = sc.localAddress(); + cb.localAddress(addr.getPort()); + + if (sc instanceof OioDatagramChannel) { + // skip the test for OIO, as it fails because of + // No route to host which makes no sense. + // Maybe a JDK bug ? + sc.close().awaitUninterruptibly(); + return; + } + DatagramChannel cc = (DatagramChannel) cb.bind().sync().channel(); + assertEquals(iface, cc.config().getNetworkInterface()); + assertInterfaceAddress(iface, cc.config().getInterface()); + + InetSocketAddress groupAddress = SocketUtils.socketAddress(groupAddress(), addr.getPort()); + + cc.joinGroup(groupAddress, iface).sync(); + + sc.writeAndFlush(new DatagramPacket(Unpooled.copyInt(1), groupAddress)).sync(); + assertTrue(mhandler.await()); + + // leave the group + cc.leaveGroup(groupAddress, iface).sync(); + + // sleep a second to make sure we left the group + Thread.sleep(1000); + + // we should not receive a message anymore as we left the group before + sc.writeAndFlush(new DatagramPacket(Unpooled.copyInt(1), groupAddress)).sync(); + mhandler.await(); + + cc.config().setLoopbackModeDisabled(false); + sc.config().setLoopbackModeDisabled(false); + + assertFalse(cc.config().isLoopbackModeDisabled()); + assertFalse(sc.config().isLoopbackModeDisabled()); + + cc.config().setLoopbackModeDisabled(true); + sc.config().setLoopbackModeDisabled(true); + + assertTrue(cc.config().isLoopbackModeDisabled()); + assertTrue(sc.config().isLoopbackModeDisabled()); + + sc.close().awaitUninterruptibly(); + cc.close().awaitUninterruptibly(); + } + + private static void assertInterfaceAddress(NetworkInterface networkInterface, InetAddress expected) { + Enumeration addresses = networkInterface.getInetAddresses(); + while (addresses.hasMoreElements()) { + if (expected.equals(addresses.nextElement())) { + return; + } + } + fail(); + } + + private static final class MulticastTestHandler extends SimpleChannelInboundHandler { + private final CountDownLatch latch = new CountDownLatch(1); + + private boolean done; + private volatile boolean fail; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { + if (done) { + fail = true; + } + + assertEquals(1, msg.content().readInt()); + + latch.countDown(); + + // mark the handler as done as we only are supposed to receive one message + done = true; + } + + public boolean await() throws Exception { + boolean success = latch.await(10, TimeUnit.SECONDS); + if (fail) { + // fail if we receive an message after we are done + fail(); + } + return success; + } + } + + @Override + protected List> newFactories() { + return SocketTestPermutation.INSTANCE.datagram(socketInternetProtocalFamily()); + } + + private InetSocketAddress newAnySocketAddress() throws UnknownHostException { + switch (socketInternetProtocalFamily()) { + case IPv4: + return new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 0); + case IPv6: + return new InetSocketAddress(InetAddress.getByName("::"), 0); + default: + throw new AssertionError(); + } + } + + private InetSocketAddress newSocketAddress(NetworkInterface iface) { + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (socketInternetProtocalFamily().addressType().isAssignableFrom(address.getClass())) { + return new InetSocketAddress(address, 0); + } + } + throw new AssertionError(); + } + + private NetworkInterface multicastNetworkInterface() throws IOException { + for (NetworkInterface iface : NetUtil.NETWORK_INTERFACES) { + if (iface.isUp() && iface.supportsMulticast()) { + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (socketInternetProtocalFamily().addressType().isAssignableFrom(address.getClass())) { + MulticastSocket socket = new MulticastSocket(newAnySocketAddress()); + socket.setReuseAddress(true); + socket.setNetworkInterface(iface); + try { + socket.send(new java.net.DatagramPacket(new byte[] { 1, 2, 3, 4 }, 4, + new InetSocketAddress(groupAddress(), 12345))); + return iface; + } catch (IOException ignore) { + // Try the next interface + } finally { + socket.close(); + } + } + } + } + } + return null; + } + + private String groupAddress() { + return groupInternetProtocalFamily() == InternetProtocolFamily.IPv4? + "230.0.0.1" : "FF01:0:0:0:0:0:0:101"; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6MappedTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6MappedTest.java new file mode 100644 index 0000000..7ba86c4 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6MappedTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.util.NetUtil; +import io.netty.util.internal.PlatformDependent; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class DatagramUnicastIPv6MappedTest extends DatagramUnicastIPv6Test { + + @Override + protected SocketAddress newSocketAddress() { + return new InetSocketAddress(0); + } + + @Override + protected InetSocketAddress sendToAddress(InetSocketAddress serverAddress) { + InetAddress addr = serverAddress.getAddress(); + if (addr.isAnyLocalAddress()) { + return new InetSocketAddress(NetUtil.LOCALHOST4, serverAddress.getPort()); + } + return serverAddress; + } + + @Override + protected boolean disconnectMightFail(DatagramChannel channel) { + // See https://bugs.openjdk.org/browse/JDK-8285515 + if (channel instanceof NioDatagramChannel && PlatformDependent.javaVersion() < 20) { + return true; + } + return super.disconnectMightFail(channel); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6Test.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6Test.java new file mode 100644 index 0000000..308669c --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastIPv6Test.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.SuppressJava6Requirement; +import org.junit.jupiter.api.BeforeAll; +import org.opentest4j.TestAbortedException; + +import java.io.IOException; +import java.net.StandardProtocolFamily; +import java.nio.channels.Channel; +import java.nio.channels.spi.SelectorProvider; + +public class DatagramUnicastIPv6Test extends DatagramUnicastInetTest { + + @SuppressJava6Requirement(reason = "Guarded by java version check") + @BeforeAll + public static void assumeIpv6Supported() { + try { + if (PlatformDependent.javaVersion() < 7) { + throw new UnsupportedOperationException(); + } + Channel channel = SelectorProvider.provider().openDatagramChannel(StandardProtocolFamily.INET6); + channel.close(); + } catch (UnsupportedOperationException e) { + throw new TestAbortedException("IPv6 not supported", e); + } catch (IOException ignore) { + // Ignore + } + } + @Override + protected InternetProtocolFamily internetProtocolFamily() { + return InternetProtocolFamily.IPv6; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastInetTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastInetTest.java new file mode 100644 index 0000000..712eb2d --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastInetTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2021 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class DatagramUnicastInetTest extends DatagramUnicastTest { + + @Test + public void testBindWithPortOnly(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testBindWithPortOnly(bootstrap2); + } + }); + } + + private static void testBindWithPortOnly(Bootstrap cb) throws Throwable { + Channel channel = null; + try { + cb.handler(new ChannelHandlerAdapter() { }); + channel = cb.bind(0).sync().channel(); + } finally { + closeChannel(channel); + } + } + + @Override + protected boolean isConnected(Channel channel) { + return ((DatagramChannel) channel).isConnected(); + } + + @Override + protected Channel setupClientChannel(Bootstrap cb, final byte[] bytes, final CountDownLatch latch, + final AtomicReference errorRef) throws Throwable { + cb.handler(new SimpleChannelInboundHandler() { + + @Override + public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) { + try { + ByteBuf buf = msg.content(); + assertEquals(bytes.length, buf.readableBytes()); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i)); + } + + InetSocketAddress localAddress = (InetSocketAddress) ctx.channel().localAddress(); + if (localAddress.getAddress().isAnyLocalAddress()) { + assertEquals(localAddress.getPort(), msg.recipient().getPort()); + } else { + // Test that the channel's localAddress is equal to the message's recipient + assertEquals(localAddress, msg.recipient()); + } + } finally { + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errorRef.compareAndSet(null, cause); + } + }); + return cb.bind(newSocketAddress()).sync().channel(); + } + + @Override + protected Channel setupServerChannel(Bootstrap sb, final byte[] bytes, final SocketAddress sender, + final CountDownLatch latch, final AtomicReference errorRef, + final boolean echo) throws Throwable { + sb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + + @Override + public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) { + try { + if (sender == null) { + assertNotNull(msg.sender()); + } else { + InetSocketAddress senderAddress = (InetSocketAddress) sender; + if (senderAddress.getAddress().isAnyLocalAddress()) { + assertEquals(senderAddress.getPort(), msg.sender().getPort()); + } else { + assertEquals(sender, msg.sender()); + } + } + + ByteBuf buf = msg.content(); + assertEquals(bytes.length, buf.readableBytes()); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], buf.getByte(buf.readerIndex() + i)); + } + + // Test that the channel's localAddress is equal to the message's recipient + assertEquals(ctx.channel().localAddress(), msg.recipient()); + + if (echo) { + ctx.writeAndFlush(new DatagramPacket(buf.retainedDuplicate(), msg.sender())); + } + } finally { + latch.countDown(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + errorRef.compareAndSet(null, cause); + } + }); + } + }); + return sb.bind(newSocketAddress()).sync().channel(); + } + + @Override + protected boolean supportDisconnect() { + return true; + } + + @Override + protected ChannelFuture write(Channel cc, ByteBuf buf, SocketAddress remote, WrapType wrapType) { + switch (wrapType) { + case DUP: + return cc.write(new DatagramPacket(buf.retainedDuplicate(), (InetSocketAddress) remote)); + case SLICE: + return cc.write(new DatagramPacket(buf.retainedSlice(), (InetSocketAddress) remote)); + case READ_ONLY: + return cc.write(new DatagramPacket(buf.retain().asReadOnly(), (InetSocketAddress) remote)); + case NONE: + return cc.write(new DatagramPacket(buf.retain(), (InetSocketAddress) remote)); + default: + throw new Error("unknown wrap type: " + wrapType); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java new file mode 100644 index 0000000..3228e13 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java @@ -0,0 +1,495 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; +import io.netty.util.internal.EmptyArrays; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.opentest4j.TestAbortedException; + +import java.net.BindException; +import java.net.DatagramSocket; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.UnresolvedAddressException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public abstract class DatagramUnicastTest extends AbstractDatagramTest { + + private static final byte[] BYTES = {0, 1, 2, 3}; + protected enum WrapType { + NONE, DUP, SLICE, READ_ONLY + } + + @Test + public void testSimpleSendDirectByteBuf(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendDirectByteBuf(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendDirectByteBuf(Bootstrap sb, Bootstrap cb) throws Throwable { + testSimpleSend(sb, cb, Unpooled.directBuffer().writeBytes(BYTES), true, BYTES, 1); + testSimpleSend(sb, cb, Unpooled.directBuffer().writeBytes(BYTES), true, BYTES, 4); + } + + @Test + public void testSimpleSendHeapByteBuf(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendHeapByteBuf(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendHeapByteBuf(Bootstrap sb, Bootstrap cb) throws Throwable { + testSimpleSend(sb, cb, Unpooled.buffer().writeBytes(BYTES), true, BYTES, 1); + testSimpleSend(sb, cb, Unpooled.buffer().writeBytes(BYTES), true, BYTES, 4); + } + + @Test + public void testSimpleSendCompositeDirectByteBuf(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendCompositeDirectByteBuf(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendCompositeDirectByteBuf(Bootstrap sb, Bootstrap cb) throws Throwable { + CompositeByteBuf buf = Unpooled.compositeBuffer(); + buf.addComponent(true, Unpooled.directBuffer().writeBytes(BYTES, 0, 2)); + buf.addComponent(true, Unpooled.directBuffer().writeBytes(BYTES, 2, 2)); + testSimpleSend(sb, cb, buf, true, BYTES, 1); + + CompositeByteBuf buf2 = Unpooled.compositeBuffer(); + buf2.addComponent(true, Unpooled.directBuffer().writeBytes(BYTES, 0, 2)); + buf2.addComponent(true, Unpooled.directBuffer().writeBytes(BYTES, 2, 2)); + testSimpleSend(sb, cb, buf2, true, BYTES, 4); + } + + @Test + public void testSimpleSendCompositeHeapByteBuf(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendCompositeHeapByteBuf(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendCompositeHeapByteBuf(Bootstrap sb, Bootstrap cb) throws Throwable { + CompositeByteBuf buf = Unpooled.compositeBuffer(); + buf.addComponent(true, Unpooled.buffer().writeBytes(BYTES, 0, 2)); + buf.addComponent(true, Unpooled.buffer().writeBytes(BYTES, 2, 2)); + testSimpleSend(sb, cb, buf, true, BYTES, 1); + + CompositeByteBuf buf2 = Unpooled.compositeBuffer(); + buf2.addComponent(true, Unpooled.buffer().writeBytes(BYTES, 0, 2)); + buf2.addComponent(true, Unpooled.buffer().writeBytes(BYTES, 2, 2)); + testSimpleSend(sb, cb, buf2, true, BYTES, 4); + } + + @Test + public void testSimpleSendCompositeMixedByteBuf(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendCompositeMixedByteBuf(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendCompositeMixedByteBuf(Bootstrap sb, Bootstrap cb) throws Throwable { + CompositeByteBuf buf = Unpooled.compositeBuffer(); + buf.addComponent(true, Unpooled.directBuffer().writeBytes(BYTES, 0, 2)); + buf.addComponent(true, Unpooled.buffer().writeBytes(BYTES, 2, 2)); + testSimpleSend(sb, cb, buf, true, BYTES, 1); + + CompositeByteBuf buf2 = Unpooled.compositeBuffer(); + buf2.addComponent(true, Unpooled.directBuffer().writeBytes(BYTES, 0, 2)); + buf2.addComponent(true, Unpooled.buffer().writeBytes(BYTES, 2, 2)); + testSimpleSend(sb, cb, buf2, true, BYTES, 4); + } + + @Test + public void testSimpleSendWithoutBind(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendWithoutBind(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendWithoutBind(Bootstrap sb, Bootstrap cb) throws Throwable { + testSimpleSend(sb, cb, Unpooled.directBuffer().writeBytes(BYTES), false, BYTES, 1); + testSimpleSend(sb, cb, Unpooled.directBuffer().writeBytes(BYTES), false, BYTES, 4); + } + + private void testSimpleSend(Bootstrap sb, Bootstrap cb, ByteBuf buf, boolean bindClient, + final byte[] bytes, int count) throws Throwable { + for (WrapType type: WrapType.values()) { + testSimpleSend0(sb, cb, buf.retain(), bindClient, bytes, count, type); + } + assertTrue(buf.release()); + } + + @Test + public void testSimpleSendWithConnect(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSimpleSendWithConnect(bootstrap, bootstrap2); + } + }); + } + + public void testSimpleSendWithConnect(Bootstrap sb, Bootstrap cb) throws Throwable { + testSimpleSendWithConnect(sb, cb, Unpooled.directBuffer().writeBytes(BYTES), BYTES, 1); + testSimpleSendWithConnect(sb, cb, Unpooled.directBuffer().writeBytes(BYTES), BYTES, 4); + } + + @Test + public void testReceiveEmptyDatagrams(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testReceiveEmptyDatagrams(bootstrap, bootstrap2); + } + }); + } + + public void testReceiveEmptyDatagrams(Bootstrap sb, Bootstrap cb) throws Throwable { + final Semaphore semaphore = new Semaphore(0); + Channel server = sb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { + semaphore.release(); + } + }); + } + }).bind(newSocketAddress()).sync().channel(); + + SocketAddress address = server.localAddress(); + DatagramSocket client; + try { + client = new DatagramSocket(newSocketAddress()); + } catch (IllegalArgumentException e) { + assumeThat(e.getMessage()).doesNotContainIgnoringCase("unsupported address type"); + throw e; + } + SocketAddress sendAddress = address instanceof InetSocketAddress ? + sendToAddress((InetSocketAddress) address) : address; + for (int i = 0; i < 100; i++) { + try { + client.send(new java.net.DatagramPacket(EmptyArrays.EMPTY_BYTES, 0, sendAddress)); + } catch (BindException e) { + throw new TestAbortedException("JDK sockets do not support binding to these addresses.", e); + } + semaphore.acquire(); + } + } + + @Test + public void testSendToUnresolvableAddress(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap, Bootstrap bootstrap2) throws Throwable { + testSendToUnresolvableAddress(bootstrap, bootstrap2); + } + }); + } + + public void testSendToUnresolvableAddress(Bootstrap sb, Bootstrap cb) throws Throwable { + SocketAddress serverAddress = newSocketAddress(); + if (!(serverAddress instanceof InetSocketAddress)) { + return; + } + Channel sc = sb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) { + // Just drop + } + }); + } + }).bind(serverAddress).sync().channel(); + + Channel cc = cb.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true). + handler(new ChannelInboundHandlerAdapter()).register().sync().channel(); + try { + InetSocketAddress goodHost = sendToAddress((InetSocketAddress) sc.localAddress()); + InetSocketAddress unresolvedHost = new InetSocketAddress("NOT_A_REAL_ADDRESS", goodHost.getPort()); + + assertFalse(goodHost.isUnresolved()); + assertTrue(unresolvedHost.isUnresolved()); + + String message = "hello world!"; + cc.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(message, CharsetUtil.US_ASCII), goodHost)).sync(); + assertInstanceOf(UnresolvedAddressException.class, cc.writeAndFlush(new DatagramPacket( + Unpooled.copiedBuffer(message, CharsetUtil.US_ASCII), unresolvedHost)).await().cause()); + + // DatagramChannel should still be open after sending to unresolved address + assertTrue(cc.isOpen()); + + // DatagramChannel should still be able to send messages outbound + cc.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(message, CharsetUtil.US_ASCII), goodHost)).sync(); + assertInstanceOf(UnresolvedAddressException.class, cc.writeAndFlush(new DatagramPacket( + Unpooled.copiedBuffer(message, CharsetUtil.US_ASCII), unresolvedHost)).await().cause()); + assertTrue(cc.isOpen()); + } finally { + closeChannel(cc); + closeChannel(sc); + } + } + + @SuppressWarnings("deprecation") + private void testSimpleSend0(Bootstrap sb, Bootstrap cb, ByteBuf buf, boolean bindClient, + final byte[] bytes, int count, WrapType wrapType) + throws Throwable { + Channel sc = null; + Channel cc = null; + + try { + cb.handler(new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msgs) { + // Nothing will be sent. + } + }); + + final SocketAddress sender; + if (bindClient) { + cc = cb.bind(newSocketAddress()).sync().channel(); + sender = cc.localAddress(); + } else { + cb.option(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true); + cc = cb.register().sync().channel(); + sender = null; + } + + final CountDownLatch latch = new CountDownLatch(count); + AtomicReference errorRef = new AtomicReference(); + sc = setupServerChannel(sb, bytes, sender, latch, errorRef, false); + + SocketAddress localAddr = sc.localAddress(); + SocketAddress addr = localAddr instanceof InetSocketAddress ? + sendToAddress((InetSocketAddress) sc.localAddress()) : localAddr; + List futures = new ArrayList(count); + for (int i = 0; i < count; i++) { + futures.add(write(cc, buf, addr, wrapType)); + } + // release as we used buf.retain() before + cc.flush(); + + for (ChannelFuture future: futures) { + future.sync(); + } + if (!latch.await(10, TimeUnit.SECONDS)) { + Throwable error = errorRef.get(); + if (error != null) { + throw error; + } + fail(); + } + } finally { + // release as we used buf.retain() before + buf.release(); + + closeChannel(cc); + closeChannel(sc); + } + } + + private void testSimpleSendWithConnect(Bootstrap sb, Bootstrap cb, ByteBuf buf, final byte[] bytes, int count) + throws Throwable { + try { + for (WrapType type : WrapType.values()) { + testSimpleSendWithConnect0(sb, cb, buf.retain(), bytes, count, type); + } + } finally { + assertTrue(buf.release()); + } + } + + private void testSimpleSendWithConnect0(Bootstrap sb, Bootstrap cb, ByteBuf buf, final byte[] bytes, int count, + WrapType wrapType) throws Throwable { + Channel sc = null; + Channel cc = null; + try { + final CountDownLatch latch = new CountDownLatch(count); + final AtomicReference errorRef = new AtomicReference(); + final CountDownLatch clientLatch = new CountDownLatch(count); + final AtomicReference clientErrorRef = new AtomicReference(); + cc = setupClientChannel(cb, bytes, clientLatch, clientErrorRef); + sc = setupServerChannel(sb, bytes, cc.localAddress(), latch, errorRef, true); + + SocketAddress localAddr = sc.localAddress(); + SocketAddress addr = localAddr instanceof InetSocketAddress ? + sendToAddress((InetSocketAddress) sc.localAddress()) : localAddr; + cc.connect(addr).syncUninterruptibly(); + + List futures = new ArrayList(); + for (int i = 0; i < count; i++) { + futures.add(write(cc, buf, wrapType)); + } + cc.flush(); + + for (ChannelFuture future: futures) { + future.sync(); + } + + if (!latch.await(10, TimeUnit.SECONDS)) { + Throwable cause = errorRef.get(); + if (cause != null) { + throw cause; + } + fail(); + } + if (!clientLatch.await(10, TimeUnit.SECONDS)) { + Throwable cause = clientErrorRef.get(); + if (cause != null) { + throw cause; + } + fail(); + } + assertTrue(isConnected(cc)); + + assertNotNull(cc.localAddress()); + assertNotNull(cc.remoteAddress()); + + if (supportDisconnect()) { + try { + // Test what happens when we call disconnect() + cc.disconnect().syncUninterruptibly(); + } catch (Throwable e) { + if (e instanceof SocketException) { + if (disconnectMightFail((DatagramChannel) cc)) { + return; + } + } + throw e; + } + assertFalse(isConnected(cc)); + assertNotNull(cc.localAddress()); + assertNull(cc.remoteAddress()); + + ChannelFuture future = cc.writeAndFlush( + buf.retain().duplicate()).awaitUninterruptibly(); + assertTrue(future.cause() instanceof NotYetConnectedException, + "NotYetConnectedException expected, got: " + future.cause()); + } + } finally { + // release as we used buf.retain() before + buf.release(); + + closeChannel(cc); + closeChannel(sc); + } + } + + private static ChannelFuture write(Channel cc, ByteBuf buf, WrapType wrapType) { + switch (wrapType) { + case DUP: + return cc.write(buf.retainedDuplicate()); + case SLICE: + return cc.write(buf.retainedSlice()); + case READ_ONLY: + return cc.write(buf.retain().asReadOnly()); + case NONE: + return cc.write(buf.retain()); + default: + throw new Error("unknown wrap type: " + wrapType); + } + } + + protected abstract boolean isConnected(Channel channel); + + protected abstract Channel setupClientChannel(Bootstrap cb, byte[] bytes, CountDownLatch latch, + AtomicReference errorRef) throws Throwable; + + protected abstract Channel setupServerChannel(Bootstrap sb, byte[] bytes, SocketAddress sender, + CountDownLatch latch, AtomicReference errorRef, + boolean echo) throws Throwable; + + protected abstract boolean supportDisconnect(); + + protected boolean disconnectMightFail(DatagramChannel channel) { + return false; + } + + protected abstract ChannelFuture write(Channel cc, ByteBuf buf, SocketAddress remote, WrapType wrapType); + + protected static void closeChannel(Channel channel) throws Exception { + if (channel != null) { + channel.close().sync(); + } + } + + protected InetSocketAddress sendToAddress(InetSocketAddress serverAddress) { + InetAddress addr = serverAddress.getAddress(); + if (addr.isAnyLocalAddress()) { + if (addr instanceof Inet6Address) { + return new InetSocketAddress(NetUtil.LOCALHOST6, serverAddress.getPort()); + } + return new InetSocketAddress(NetUtil.LOCALHOST4, serverAddress.getPort()); + } + return serverAddress; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java new file mode 100644 index 0000000..db88ed8 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.util.internal.SocketUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServerSocketSuspendTest extends AbstractServerSocketTest { + + private static final int NUM_CHANNELS = 10; + private static final long TIMEOUT = 3000000000L; + + @Test + @Disabled("Need to investigate why it fails on osx") + public void testSuspendAndResumeAccept(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap) throws Throwable { + testSuspendAndResumeAccept(serverBootstrap); + } + }); + } + + public void testSuspendAndResumeAccept(ServerBootstrap sb) throws Throwable { + AcceptedChannelCounter counter = new AcceptedChannelCounter(NUM_CHANNELS); + + sb.option(ChannelOption.SO_BACKLOG, 1); + sb.option(ChannelOption.AUTO_READ, false); + sb.childHandler(counter); + + Channel sc = sb.bind().sync().channel(); + + List sockets = new ArrayList(); + + try { + long startTime = System.nanoTime(); + for (int i = 0; i < NUM_CHANNELS; i ++) { + Socket s = new Socket(); + SocketUtils.connect(s, sc.localAddress(), 10000); + sockets.add(s); + } + + sc.config().setAutoRead(true); + + counter.latch.await(); + + long endTime = System.nanoTime(); + assertTrue(endTime - startTime > TIMEOUT); + } finally { + for (Socket s: sockets) { + s.close(); + } + } + + Thread.sleep(TIMEOUT / 1000000); + + try { + long startTime = System.nanoTime(); + for (int i = 0; i < NUM_CHANNELS; i ++) { + Socket s = new Socket(); + s.connect(sc.localAddress(), 10000); + sockets.add(s); + } + long endTime = System.nanoTime(); + + assertTrue(endTime - startTime < TIMEOUT); + } finally { + for (Socket s: sockets) { + s.close(); + } + } + } + + @ChannelHandler.Sharable + private static final class AcceptedChannelCounter extends ChannelInboundHandlerAdapter { + + final CountDownLatch latch; + + AcceptedChannelCounter(int nChannels) { + latch = new CountDownLatch(nChannels); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + latch.countDown(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAddressesTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAddressesTest.java new file mode 100644 index 0000000..dce8c08 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAddressesTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2023 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.net.SocketAddress; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertNull; + +public abstract class SocketAddressesTest extends AbstractSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testAddresses(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAddresses(serverBootstrap, bootstrap, true); + } + }); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testAddressesConnectWithoutLocalAddress(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAddresses(serverBootstrap, bootstrap, false); + } + }); + } + + protected abstract void assertAddress(SocketAddress address); + + private void testAddresses(ServerBootstrap sb, Bootstrap cb, boolean withLocalAddress) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + final Promise localAddressPromise = ImmediateEventExecutor.INSTANCE.newPromise(); + final Promise remoteAddressPromise = ImmediateEventExecutor.INSTANCE.newPromise(); + serverChannel = sb.childHandler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) { + localAddressPromise.setSuccess(ctx.channel().localAddress()); + remoteAddressPromise.setSuccess(ctx.channel().remoteAddress()); + } + }).bind().syncUninterruptibly().channel(); + + clientChannel = cb.handler(new ChannelInboundHandlerAdapter()).register().syncUninterruptibly().channel(); + + assertNull(clientChannel.localAddress()); + assertNull(clientChannel.remoteAddress()); + + if (withLocalAddress) { + clientChannel.connect(serverChannel.localAddress(), newSocketAddress()).syncUninterruptibly().channel(); + } else { + clientChannel.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + } + + assertAddress(clientChannel.localAddress()); + assertAddress(clientChannel.remoteAddress()); + + assertAddress(localAddressPromise.get()); + assertAddress(remoteAddressPromise.get()); + } finally { + if (clientChannel != null) { + clientChannel.close().syncUninterruptibly(); + } + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAutoReadTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAutoReadTest.java new file mode 100644 index 0000000..882aec4 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketAutoReadTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.UncheckedBooleanSupplier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketAutoReadTest extends AbstractSocketTest { + @Test + public void testAutoReadOffDuringReadOnlyReadsOneTime(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAutoReadOffDuringReadOnlyReadsOneTime(serverBootstrap, bootstrap); + } + }); + } + + public void testAutoReadOffDuringReadOnlyReadsOneTime(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testAutoReadOffDuringReadOnlyReadsOneTime(true, sb, cb); + testAutoReadOffDuringReadOnlyReadsOneTime(false, sb, cb); + } + + private static void testAutoReadOffDuringReadOnlyReadsOneTime(boolean readOutsideEventLoopThread, + ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + AutoReadInitializer serverInitializer = new AutoReadInitializer(!readOutsideEventLoopThread); + AutoReadInitializer clientInitializer = new AutoReadInitializer(!readOutsideEventLoopThread); + sb.option(ChannelOption.SO_BACKLOG, 1024) + .option(ChannelOption.AUTO_READ, true) + .childOption(ChannelOption.AUTO_READ, true) + // We want to ensure that we attempt multiple individual read operations per read loop so we can + // test the auto read feature being turned off when data is first read. + .childOption(ChannelOption.RCVBUF_ALLOCATOR, new TestRecvByteBufAllocator()) + .childHandler(serverInitializer); + + serverChannel = sb.bind().syncUninterruptibly().channel(); + + cb.option(ChannelOption.AUTO_READ, true) + // We want to ensure that we attempt multiple individual read operations per read loop so we can + // test the auto read feature being turned off when data is first read. + .option(ChannelOption.RCVBUF_ALLOCATOR, new TestRecvByteBufAllocator()) + .handler(clientInitializer); + + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + + // 3 bytes means 3 independent reads for TestRecvByteBufAllocator + clientChannel.writeAndFlush(Unpooled.wrappedBuffer(new byte[3])); + serverInitializer.autoReadHandler.assertSingleRead(); + + // 3 bytes means 3 independent reads for TestRecvByteBufAllocator + serverInitializer.channel.writeAndFlush(Unpooled.wrappedBuffer(new byte[3])); + clientInitializer.autoReadHandler.assertSingleRead(); + + if (readOutsideEventLoopThread) { + serverInitializer.channel.read(); + } + serverInitializer.autoReadHandler.assertSingleReadSecondTry(); + + if (readOutsideEventLoopThread) { + clientChannel.read(); + } + clientInitializer.autoReadHandler.assertSingleReadSecondTry(); + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + private static class AutoReadInitializer extends ChannelInitializer { + final AutoReadHandler autoReadHandler; + volatile Channel channel; + + AutoReadInitializer(boolean readInEventLoop) { + autoReadHandler = new AutoReadHandler(readInEventLoop); + } + + @Override + protected void initChannel(Channel ch) throws Exception { + channel = ch; + ch.pipeline().addLast(autoReadHandler); + } + } + + private static final class AutoReadHandler extends ChannelInboundHandlerAdapter { + private final AtomicInteger count = new AtomicInteger(); + private final CountDownLatch latch = new CountDownLatch(1); + private final CountDownLatch latch2; + private final boolean callRead; + + AutoReadHandler(boolean callRead) { + this.callRead = callRead; + latch2 = new CountDownLatch(callRead ? 3 : 2); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ReferenceCountUtil.release(msg); + if (count.incrementAndGet() == 1) { + ctx.channel().config().setAutoRead(false); + } + if (callRead) { + // Test calling read in the EventLoop thread to ensure a read is eventually done. + ctx.read(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + latch.countDown(); + latch2.countDown(); + } + + void assertSingleRead() throws InterruptedException { + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(count.get() > 0); + } + + void assertSingleReadSecondTry() throws InterruptedException { + assertTrue(latch2.await(5, TimeUnit.SECONDS)); + assertEquals(callRead ? 3 : 2, count.get()); + } + } + + /** + * Designed to keep reading as long as autoread is enabled. + */ + private static final class TestRecvByteBufAllocator implements RecvByteBufAllocator { + @Override + public ExtendedHandle newHandle() { + return new ExtendedHandle() { + private ChannelConfig config; + private int attemptedBytesRead; + private int lastBytesRead; + @Override + public ByteBuf allocate(ByteBufAllocator alloc) { + return alloc.ioBuffer(guess(), guess()); + } + + @Override + public int guess() { + return 1; // only ever allocate buffers of size 1 to ensure the number of reads is controlled. + } + + @Override + public void reset(ChannelConfig config) { + this.config = config; + } + + @Override + public void incMessagesRead(int numMessages) { + // No need to track the number of messages read because it is not used. + } + + @Override + public void lastBytesRead(int bytes) { + lastBytesRead = bytes; + } + + @Override + public int lastBytesRead() { + return lastBytesRead; + } + + @Override + public void attemptedBytesRead(int bytes) { + attemptedBytesRead = bytes; + } + + @Override + public int attemptedBytesRead() { + return attemptedBytesRead; + } + + @Override + public boolean continueReading() { + return config.isAutoRead(); + } + + @Override + public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) { + return config.isAutoRead(); + } + + @Override + public void readComplete() { + // Nothing needs to be done or adjusted after each read cycle is completed. + } + }; + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketBufReleaseTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketBufReleaseTest.java new file mode 100644 index 0000000..11f9299 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketBufReleaseTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.DefaultPromise; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Promise; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SocketBufReleaseTest extends AbstractSocketTest { + + private static final EventExecutor executor = + new DefaultEventExecutorGroup(1, new DefaultThreadFactory(SocketBufReleaseTest.class, true)).next(); + + @Test + public void testBufRelease(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testBufRelease(serverBootstrap, bootstrap); + } + }); + } + + public void testBufRelease(ServerBootstrap sb, Bootstrap cb) throws Throwable { + BufWriterHandler serverHandler = new BufWriterHandler(); + BufWriterHandler clientHandler = new BufWriterHandler(); + + sb.childHandler(serverHandler); + cb.handler(clientHandler); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + // Ensure the server socket accepted the client connection *and* initialized pipeline successfully. + serverHandler.channelFuture.sync(); + + // and then close all sockets. + sc.close().sync(); + cc.close().sync(); + + serverHandler.check(); + clientHandler.check(); + + serverHandler.release(); + clientHandler.release(); + } + + private static class BufWriterHandler extends SimpleChannelInboundHandler { + + private final Random random = new Random(); + private final CountDownLatch latch = new CountDownLatch(1); + private ByteBuf buf; + private final Promise channelFuture = new DefaultPromise(executor); + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + channelFuture.setSuccess(ctx.channel()); + } + + @Override + public void channelActive(final ChannelHandlerContext ctx) throws Exception { + byte[] data = new byte[1024]; + random.nextBytes(data); + + buf = ctx.alloc().buffer(); + // call retain on it so it can't be put back on the pool + buf.writeBytes(data).retain(); + + ctx.channel().writeAndFlush(buf).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + latch.countDown(); + } + }); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + // discard + } + + public void check() throws InterruptedException { + latch.await(); + assertEquals(1, buf.refCnt()); + } + + void release() { + buf.release(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCancelWriteTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCancelWriteTest.java new file mode 100644 index 0000000..a95f42c --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCancelWriteTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketCancelWriteTest extends AbstractSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testCancelWrite(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testCancelWrite(serverBootstrap, bootstrap); + } + }); + } + + public void testCancelWrite(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final TestHandler sh = new TestHandler(); + final TestHandler ch = new TestHandler(); + final ByteBuf a = Unpooled.buffer().writeByte('a'); + final ByteBuf b = Unpooled.buffer().writeByte('b'); + final ByteBuf c = Unpooled.buffer().writeByte('c'); + final ByteBuf d = Unpooled.buffer().writeByte('d'); + final ByteBuf e = Unpooled.buffer().writeByte('e'); + + cb.handler(ch); + sb.childHandler(sh); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + ChannelFuture f = cc.write(a); + assertTrue(f.cancel(false)); + cc.writeAndFlush(b); + cc.write(c); + ChannelFuture f2 = cc.write(d); + assertTrue(f2.cancel(false)); + cc.writeAndFlush(e); + + while (sh.counter < 3) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + Thread.sleep(50); + } + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + assertEquals(0, ch.counter); + assertEquals(Unpooled.wrappedBuffer(new byte[]{'b', 'c', 'e'}), sh.received); + } + + private static class TestHandler extends SimpleChannelInboundHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + final ByteBuf received = Unpooled.buffer(); + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + counter += in.readableBytes(); + received.writeBytes(in); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketChannelNotYetConnectedTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketChannelNotYetConnectedTest.java new file mode 100644 index 0000000..13171a8 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketChannelNotYetConnectedTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.ByteToMessageDecoder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.net.SocketException; +import java.nio.channels.NotYetConnectedException; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +public class SocketChannelNotYetConnectedTest extends AbstractClientSocketTest { + @Test + @Timeout(30) + public void testShutdownNotYetConnected(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testShutdownNotYetConnected(bootstrap); + } + }); + } + + public void testShutdownNotYetConnected(Bootstrap cb) throws Throwable { + SocketChannel ch = (SocketChannel) cb.handler(new ChannelInboundHandlerAdapter()) + .bind(newSocketAddress()).syncUninterruptibly().channel(); + try { + try { + ch.shutdownInput().syncUninterruptibly(); + fail(); + } catch (Throwable cause) { + checkThrowable(cause); + } + + try { + ch.shutdownOutput().syncUninterruptibly(); + fail(); + } catch (Throwable cause) { + checkThrowable(cause); + } + } finally { + ch.close().syncUninterruptibly(); + } + } + + private static void checkThrowable(Throwable cause) throws Throwable { + // Depending on OIO / NIO both are ok + if (!(cause instanceof NotYetConnectedException) && !(cause instanceof SocketException)) { + throw cause; + } + } + + @Test + @Timeout(30) + public void readMustBePendingUntilChannelIsActive(TestInfo info) throws Throwable { + run(info, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + NioEventLoopGroup group = new NioEventLoopGroup(1); + ServerBootstrap sb = new ServerBootstrap().group(group); + Channel serverChannel = sb.childHandler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.writeAndFlush(Unpooled.copyInt(42)); + } + }).channel(NioServerSocketChannel.class).bind(0).sync().channel(); + + final CountDownLatch readLatch = new CountDownLatch(1); + bootstrap.handler(new ByteToMessageDecoder() { + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + assertFalse(ctx.channel().isActive()); + ctx.read(); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + assertThat(in.readableBytes()).isLessThanOrEqualTo(Integer.BYTES); + if (in.readableBytes() == Integer.BYTES) { + assertThat(in.readInt()).isEqualTo(42); + readLatch.countDown(); + } + } + }); + bootstrap.connect(serverChannel.localAddress()).sync(); + + readLatch.await(); + group.shutdownGracefully().await(); + } + }); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCloseForciblyTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCloseForciblyTest.java new file mode 100644 index 0000000..bd50b23 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketCloseForciblyTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.SocketChannel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class SocketCloseForciblyTest extends AbstractSocketTest { + + @Test + public void testCloseForcibly(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testCloseForcibly(serverBootstrap, bootstrap); + } + }); + } + + public void testCloseForcibly(ServerBootstrap sb, Bootstrap cb) throws Throwable { + sb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + SocketChannel childChannel = (SocketChannel) msg; + childChannel.config().setSoLinger(0); + childChannel.unsafe().closeForcibly(); + } + }).childHandler(new ChannelInboundHandlerAdapter()); + + cb.handler(new ChannelInboundHandlerAdapter()); + + Channel sc = sb.bind().sync().channel(); + + cb.connect(sc.localAddress()).channel().closeFuture().syncUninterruptibly(); + sc.close().sync(); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConditionalWritabilityTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConditionalWritabilityTest.java new file mode 100644 index 0000000..2c6bb4a --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConditionalWritabilityTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.util.ReferenceCountUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class SocketConditionalWritabilityTest extends AbstractSocketTest { + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testConditionalWritability(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testConditionalWritability(serverBootstrap, bootstrap); + } + }); + } + + public void testConditionalWritability(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + final int expectedBytes = 100 * 1024 * 1024; + final int maxWriteChunkSize = 16 * 1024; + final CountDownLatch latch = new CountDownLatch(1); + sb.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(8 * 1024, 16 * 1024)); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new ChannelDuplexHandler() { + private int bytesWritten; + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ReferenceCountUtil.release(msg); + writeRemainingBytes(ctx); + } + + @Override + public void flush(ChannelHandlerContext ctx) { + if (ctx.channel().isWritable()) { + writeRemainingBytes(ctx); + } else { + ctx.flush(); + } + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) { + if (ctx.channel().isWritable()) { + writeRemainingBytes(ctx); + } + ctx.fireChannelWritabilityChanged(); + } + + private void writeRemainingBytes(ChannelHandlerContext ctx) { + while (ctx.channel().isWritable() && bytesWritten < expectedBytes) { + int chunkSize = Math.min(expectedBytes - bytesWritten, maxWriteChunkSize); + bytesWritten += chunkSize; + ctx.write(ctx.alloc().buffer(chunkSize).writeZero(chunkSize)); + } + ctx.flush(); + } + }); + } + }); + + serverChannel = sb.bind().syncUninterruptibly().channel(); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + private int totalRead; + @Override + public void channelActive(ChannelHandlerContext ctx) { + ctx.writeAndFlush(ctx.alloc().buffer(1).writeByte(0)); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof ByteBuf) { + totalRead += ((ByteBuf) msg).readableBytes(); + if (totalRead == expectedBytes) { + latch.countDown(); + } + } + ReferenceCountUtil.release(msg); + } + }); + } + }); + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + latch.await(); + } finally { + if (serverChannel != null) { + serverChannel.close(); + } + if (clientChannel != null) { + clientChannel.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java new file mode 100644 index 0000000..06ba85f --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectTest.java @@ -0,0 +1,239 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.socket.SocketChannel; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import static io.netty.buffer.ByteBufUtil.writeAscii; +import static io.netty.buffer.UnpooledByteBufAllocator.DEFAULT; +import static io.netty.util.CharsetUtil.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketConnectTest extends AbstractSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testLocalAddressAfterConnect(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testLocalAddressAfterConnect(serverBootstrap, bootstrap); + } + }); + } + + public void testLocalAddressAfterConnect(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + final Promise localAddressPromise = ImmediateEventExecutor.INSTANCE.newPromise(); + serverChannel = sb.childHandler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + localAddressPromise.setSuccess((InetSocketAddress) ctx.channel().localAddress()); + } + }).bind().syncUninterruptibly().channel(); + + clientChannel = cb.handler(new ChannelInboundHandlerAdapter()).register().syncUninterruptibly().channel(); + + assertNull(clientChannel.localAddress()); + assertNull(clientChannel.remoteAddress()); + + clientChannel.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + assertLocalAddress((InetSocketAddress) clientChannel.localAddress()); + assertNotNull(clientChannel.remoteAddress()); + + assertLocalAddress(localAddressPromise.get()); + } finally { + if (clientChannel != null) { + clientChannel.close().syncUninterruptibly(); + } + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testChannelEventsFiredWhenClosedDirectly(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testChannelEventsFiredWhenClosedDirectly(serverBootstrap, bootstrap); + } + }); + } + + public void testChannelEventsFiredWhenClosedDirectly(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final BlockingQueue events = new LinkedBlockingQueue(); + + Channel sc = null; + Channel cc = null; + try { + sb.childHandler(new ChannelInboundHandlerAdapter()); + sc = sb.bind().syncUninterruptibly().channel(); + + cb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + events.add(0); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + events.add(1); + } + }); + // Connect and directly close again. + cc = cb.connect(sc.localAddress()).addListener(ChannelFutureListener.CLOSE). + syncUninterruptibly().channel(); + assertEquals(0, events.take().intValue()); + assertEquals(1, events.take().intValue()); + } finally { + if (cc != null) { + cc.close(); + } + if (sc != null) { + sc.close(); + } + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testWriteWithFastOpenBeforeConnect(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testWriteWithFastOpenBeforeConnect(serverBootstrap, bootstrap); + } + }); + } + + public void testWriteWithFastOpenBeforeConnect(ServerBootstrap sb, Bootstrap cb) throws Throwable { + enableTcpFastOpen(sb, cb); + sb.childOption(ChannelOption.AUTO_READ, true); + cb.option(ChannelOption.AUTO_READ, true); + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new EchoServerHandler()); + } + }); + + Channel sc = sb.bind().sync().channel(); + connectAndVerifyDataTransfer(cb, sc); + connectAndVerifyDataTransfer(cb, sc); + } + + private static void connectAndVerifyDataTransfer(Bootstrap cb, Channel sc) + throws InterruptedException { + BufferingClientHandler handler = new BufferingClientHandler(); + cb.handler(handler); + ChannelFuture register = cb.register(); + Channel channel = register.sync().channel(); + ChannelFuture write = channel.write(writeAscii(DEFAULT, "[fastopen]")); + SocketAddress remoteAddress = sc.localAddress(); + ChannelFuture connectFuture = channel.connect(remoteAddress); + Channel cc = connectFuture.sync().channel(); + cc.writeAndFlush(writeAscii(DEFAULT, "[normal data]")).sync(); + write.sync(); + String expectedString = "[fastopen][normal data]"; + String result = handler.collectBuffer(expectedString.getBytes(US_ASCII).length); + cc.disconnect().sync(); + assertEquals(expectedString, result); + } + + protected void enableTcpFastOpen(ServerBootstrap sb, Bootstrap cb) { + // TFO is an almost-pure optimisation and should not change any observable behaviour in our tests. + sb.option(ChannelOption.TCP_FASTOPEN, 5); + cb.option(ChannelOption.TCP_FASTOPEN_CONNECT, true); + } + + private static void assertLocalAddress(InetSocketAddress address) { + assertTrue(address.getPort() > 0); + assertFalse(address.getAddress().isAnyLocalAddress()); + } + + private static class BufferingClientHandler extends ChannelInboundHandlerAdapter { + private final Semaphore semaphore = new Semaphore(0); + private final ByteArrayOutputStream streamBuffer = new ByteArrayOutputStream(); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof ByteBuf) { + ByteBuf buf = (ByteBuf) msg; + int readableBytes = buf.readableBytes(); + buf.readBytes(streamBuffer, readableBytes); + semaphore.release(readableBytes); + buf.release(); + } else { + throw new IllegalArgumentException("Unexpected message type: " + msg); + } + } + + String collectBuffer(int expectedBytes) throws InterruptedException { + semaphore.acquire(expectedBytes); + byte[] bytes = streamBuffer.toByteArray(); + streamBuffer.reset(); + return new String(bytes, US_ASCII); + } + } + + private static final class EchoServerHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof ByteBuf) { + ByteBuf buffer = ctx.alloc().buffer(); + ByteBuf buf = (ByteBuf) msg; + buffer.writeBytes(buf); + buf.release(); + ctx.channel().writeAndFlush(buffer); + } else { + throw new IllegalArgumentException("Unexpected message type: " + msg); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectionAttemptTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectionAttemptTest.java new file mode 100644 index 0000000..b253e4d --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketConnectionAttemptTest.java @@ -0,0 +1,178 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.util.internal.SocketUtils; +import io.netty.util.NetUtil; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import static io.netty.testsuite.transport.socket.SocketTestPermutation.BAD_HOST; +import static io.netty.testsuite.transport.socket.SocketTestPermutation.BAD_PORT; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class SocketConnectionAttemptTest extends AbstractClientSocketTest { + + // See /etc/services + private static final int UNASSIGNED_PORT = 4; + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testConnectTimeout(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testConnectTimeout(bootstrap); + } + }); + } + + public void testConnectTimeout(Bootstrap cb) throws Throwable { + cb.handler(new TestHandler()).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000); + ChannelFuture future = cb.connect(BAD_HOST, BAD_PORT); + try { + assertThat(future.await(3000), is(true)); + } finally { + future.channel().close(); + } + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testConnectRefused(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testConnectRefused(bootstrap); + } + }); + } + + public void testConnectRefused(Bootstrap cb) throws Throwable { + testConnectRefused0(cb, false); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testConnectRefusedHalfClosure(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testConnectRefusedHalfClosure(bootstrap); + } + }); + } + + public void testConnectRefusedHalfClosure(Bootstrap cb) throws Throwable { + testConnectRefused0(cb, true); + } + + private static void testConnectRefused0(Bootstrap cb, boolean halfClosure) throws Throwable { + final Promise errorPromise = GlobalEventExecutor.INSTANCE.newPromise(); + ChannelHandler handler = new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + errorPromise.setFailure(new AssertionError("should have never been called")); + } + }; + + cb.handler(handler); + cb.option(ChannelOption.ALLOW_HALF_CLOSURE, halfClosure); + ChannelFuture future = cb.connect(NetUtil.LOCALHOST, UNASSIGNED_PORT).awaitUninterruptibly(); + assertThat(future.cause(), is(instanceOf(ConnectException.class))); + assertThat(errorPromise.cause(), is(nullValue())); + } + + @Test + public void testConnectCancellation(TestInfo testInfo) throws Throwable { + // Check if the test can be executed or should be skipped because of no network/internet connection + // See https://github.com/netty/netty/issues/1474 + boolean badHostTimedOut = true; + Socket socket = new Socket(); + try { + SocketUtils.connect(socket, SocketUtils.socketAddress(BAD_HOST, BAD_PORT), 10); + } catch (ConnectException e) { + badHostTimedOut = false; + // is thrown for no route to host when using Socket connect + } catch (Exception e) { + // ignore + } finally { + try { + socket.close(); + } catch (IOException e) { + // ignore + } + } + + assumeTrue(badHostTimedOut, "The connection attempt to " + BAD_HOST + " does not time out."); + + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testConnectCancellation(bootstrap); + } + }); + } + + public void testConnectCancellation(Bootstrap cb) throws Throwable { + cb.handler(new TestHandler()).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000); + ChannelFuture future = cb.connect(BAD_HOST, BAD_PORT); + try { + if (future.await(1000)) { + if (future.isSuccess()) { + fail("A connection attempt to " + BAD_HOST + " must not succeed."); + } else { + throw future.cause(); + } + } + + if (future.cancel(true)) { + assertThat(future.channel().closeFuture().await(500), is(true)); + assertThat(future.isCancelled(), is(true)); + } else { + // Cancellation not supported by the transport. + } + } finally { + future.channel().close(); + } + } + + private static class TestHandler extends ChannelInboundHandlerAdapter { + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + InternalLoggerFactory.getInstance( + SocketConnectionAttemptTest.class).warn("Unexpected exception:", cause); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketDataReadInitialStateTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketDataReadInitialStateTest.java new file mode 100644 index 0000000..1708042 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketDataReadInitialStateTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2018 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.SimpleChannelInboundHandler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static io.netty.channel.ChannelOption.AUTO_READ; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class SocketDataReadInitialStateTest extends AbstractSocketTest { + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testAutoReadOffNoDataReadUntilReadCalled(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAutoReadOffNoDataReadUntilReadCalled(serverBootstrap, bootstrap); + } + }); + } + + public void testAutoReadOffNoDataReadUntilReadCalled(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + final int sleepMs = 100; + try { + sb.option(AUTO_READ, false); + sb.childOption(AUTO_READ, false); + cb.option(AUTO_READ, false); + final CountDownLatch serverReadyLatch = new CountDownLatch(1); + final CountDownLatch acceptorReadLatch = new CountDownLatch(1); + final CountDownLatch serverReadLatch = new CountDownLatch(1); + final CountDownLatch clientReadLatch = new CountDownLatch(1); + final AtomicReference serverConnectedChannelRef = new AtomicReference(); + + sb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + acceptorReadLatch.countDown(); + ctx.fireChannelRead(msg); + } + }); + } + }); + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + serverConnectedChannelRef.set(ch); + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + ctx.writeAndFlush(msg.retainedDuplicate()); + serverReadLatch.countDown(); + } + }); + serverReadyLatch.countDown(); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + clientReadLatch.countDown(); + } + }); + } + }); + + serverChannel = sb.bind().sync().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + clientChannel.writeAndFlush(clientChannel.alloc().buffer().writeZero(1)).syncUninterruptibly(); + + // The acceptor shouldn't read any data until we call read() below, but give it some time to see if it will. + Thread.sleep(sleepMs); + assertEquals(1, acceptorReadLatch.getCount()); + serverChannel.read(); + serverReadyLatch.await(); + + Channel serverConnectedChannel = serverConnectedChannelRef.get(); + assertNotNull(serverConnectedChannel); + + // Allow some amount of time for the server peer to receive the message (which isn't expected to happen + // until we call read() below). + Thread.sleep(sleepMs); + assertEquals(1, serverReadLatch.getCount()); + serverConnectedChannel.read(); + serverReadLatch.await(); + + // Allow some amount of time for the client to read the echo. + Thread.sleep(sleepMs); + assertEquals(1, clientReadLatch.getCount()); + clientChannel.read(); + clientReadLatch.await(); + } finally { + if (serverChannel != null) { + serverChannel.close().sync(); + } + if (clientChannel != null) { + clientChannel.close().sync(); + } + } + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testAutoReadOnDataReadImmediately(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAutoReadOnDataReadImmediately(serverBootstrap, bootstrap); + } + }); + } + + public void testAutoReadOnDataReadImmediately(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + sb.option(AUTO_READ, true); + sb.childOption(AUTO_READ, true); + cb.option(AUTO_READ, true); + final CountDownLatch serverReadLatch = new CountDownLatch(1); + final CountDownLatch clientReadLatch = new CountDownLatch(1); + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + ctx.writeAndFlush(msg.retainedDuplicate()); + serverReadLatch.countDown(); + } + }); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + clientReadLatch.countDown(); + } + }); + } + }); + + serverChannel = sb.bind().sync().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + clientChannel.writeAndFlush(clientChannel.alloc().buffer().writeZero(1)).syncUninterruptibly(); + serverReadLatch.await(); + clientReadLatch.await(); + } finally { + if (serverChannel != null) { + serverChannel.close().sync(); + } + if (clientChannel != null) { + clientChannel.close().sync(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketEchoTest.java new file mode 100644 index 0000000..b9a7997 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketEchoTest.java @@ -0,0 +1,310 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class SocketEchoTest extends AbstractSocketTest { + + private static final Random random = new Random(); + static final byte[] data = new byte[1048576]; + + private static EventExecutorGroup group; + + static { + random.nextBytes(data); + } + + @BeforeAll + public static void createGroup() { + group = new DefaultEventExecutorGroup(2); + } + + @AfterAll + public static void destroyGroup() throws Exception { + group.shutdownGracefully().sync(); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSimpleEcho(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSimpleEcho(serverBootstrap, bootstrap); + } + }); + } + + public void testSimpleEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, false, false, true); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSimpleEchoNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap sb1, Bootstrap cb1) throws Throwable { + testSimpleEchoNotAutoRead(sb1, cb1); + } + }); + } + + public void testSimpleEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, false, false, false); + } + + @Test + public void testSimpleEchoWithAdditionalExecutor(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap sb1, Bootstrap cb1) throws Throwable { + testSimpleEchoWithAdditionalExecutor(sb1, cb1); + } + }); + } + + public void testSimpleEchoWithAdditionalExecutor(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, true, false, true); + } + + @Test + public void testSimpleEchoWithAdditionalExecutorNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap sb1, Bootstrap cb1) throws Throwable { + testSimpleEchoWithAdditionalExecutorNotAutoRead(sb1, cb1); + } + }); + } + + public void testSimpleEchoWithAdditionalExecutorNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, true, false, false); + } + + @Test + public void testSimpleEchoWithVoidPromise(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap sb1, Bootstrap cb1) throws Throwable { + testSimpleEchoWithVoidPromise(sb1, cb1); + } + }); + } + + public void testSimpleEchoWithVoidPromise(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, false, true, true); + } + + @Test + public void testSimpleEchoWithVoidPromiseNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap sb1, Bootstrap cb1) throws Throwable { + testSimpleEchoWithVoidPromiseNotAutoRead(sb1, cb1); + } + }); + } + + public void testSimpleEchoWithVoidPromiseNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, false, true, false); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSimpleEchoWithAdditionalExecutorAndVoidPromise(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap sb1, Bootstrap cb1) throws Throwable { + testSimpleEchoWithAdditionalExecutorAndVoidPromise(sb1, cb1); + } + }); + } + + public void testSimpleEchoWithAdditionalExecutorAndVoidPromise(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, true, true, true); + } + + private static void testSimpleEcho0( + ServerBootstrap sb, Bootstrap cb, boolean additionalExecutor, boolean voidPromise, boolean autoRead) + throws Throwable { + + final EchoHandler sh = new EchoHandler(autoRead); + final EchoHandler ch = new EchoHandler(autoRead); + + if (additionalExecutor) { + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel c) throws Exception { + c.pipeline().addLast(group, sh); + } + }); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel c) throws Exception { + c.pipeline().addLast(group, ch); + } + }); + } else { + sb.childHandler(sh); + sb.handler(new ChannelInboundHandlerAdapter() { + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + } + }); + cb.handler(ch); + } + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + ByteBuf buf = Unpooled.wrappedBuffer(data, i, length); + if (voidPromise) { + assertEquals(cc.voidPromise(), cc.writeAndFlush(buf, cc.voidPromise())); + } else { + assertNotEquals(cc.voidPromise(), cc.writeAndFlush(buf)); + } + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelInboundHandler { + private final boolean autoRead; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler(boolean autoRead) { + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.parent() != null) { + channel.write(Unpooled.wrappedBuffer(actual)); + } + + counter += actual.length; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + try { + ctx.flush(); + } finally { + if (!autoRead) { + ctx.read(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + cause.printStackTrace(); + ctx.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketExceptionHandlingTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketExceptionHandlingTest.java new file mode 100644 index 0000000..c611563 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketExceptionHandlingTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.util.ReferenceCountUtil; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.jupiter.api.TestInfo; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketExceptionHandlingTest extends AbstractSocketTest { + @Test + public void testReadPendingIsResetAfterEachRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testReadPendingIsResetAfterEachRead(serverBootstrap, bootstrap); + } + }); + } + + public void testReadPendingIsResetAfterEachRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + MyInitializer serverInitializer = new MyInitializer(); + sb.option(ChannelOption.SO_BACKLOG, 1024); + sb.childHandler(serverInitializer); + + serverChannel = sb.bind().syncUninterruptibly().channel(); + + cb.handler(new MyInitializer()); + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + + clientChannel.writeAndFlush(Unpooled.wrappedBuffer(new byte[1024])); + + // We expect to get 2 exceptions (1 from BuggyChannelHandler and 1 from ExceptionHandler). + assertTrue(serverInitializer.exceptionHandler.latch1.await(5, TimeUnit.SECONDS)); + + // After we get the first exception, we should get no more, this is expected to timeout. + assertFalse(serverInitializer.exceptionHandler.latch2.await(1, TimeUnit.SECONDS), + "Encountered " + serverInitializer.exceptionHandler.count.get() + + " exceptions when 1 was expected"); + } finally { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + if (clientChannel != null) { + clientChannel.close().syncUninterruptibly(); + } + } + } + + private static class MyInitializer extends ChannelInitializer { + final ExceptionHandler exceptionHandler = new ExceptionHandler(); + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + + pipeline.addLast(new BuggyChannelHandler()); + pipeline.addLast(exceptionHandler); + } + } + + private static class BuggyChannelHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ReferenceCountUtil.release(msg); + throw new NullPointerException("I am a bug!"); + } + } + + private static class ExceptionHandler extends ChannelInboundHandlerAdapter { + final AtomicLong count = new AtomicLong(); + /** + * We expect to get 1 call to {@link #exceptionCaught(ChannelHandlerContext, Throwable)}. + */ + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (count.incrementAndGet() <= 2) { + latch1.countDown(); + } else { + latch2.countDown(); + } + // This should not throw any exception. + ctx.close(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java new file mode 100644 index 0000000..c6b2235 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java @@ -0,0 +1,384 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.DefaultFileRegion; +import io.netty.channel.FileRegion; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.internal.PlatformDependent; +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.WritableByteChannel; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class SocketFileRegionTest extends AbstractSocketTest { + + static final byte[] data = new byte[1048576 * 10]; + + static { + PlatformDependent.threadLocalRandom().nextBytes(data); + } + + @Test + public void testFileRegion(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFileRegion(serverBootstrap, bootstrap); + } + }); + } + + @Test + public void testCustomFileRegion(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testCustomFileRegion(serverBootstrap, bootstrap); + } + }); + } + + @Test + public void testFileRegionNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFileRegionNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + @Test + public void testFileRegionVoidPromise(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFileRegionVoidPromise(serverBootstrap, bootstrap); + } + }); + } + + @Test + public void testFileRegionVoidPromiseNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFileRegionVoidPromiseNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + @Test + public void testFileRegionCountLargerThenFile(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFileRegionCountLargerThenFile(serverBootstrap, bootstrap); + } + }); + } + + public void testFileRegion(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFileRegion0(sb, cb, false, true, true); + } + + public void testCustomFileRegion(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFileRegion0(sb, cb, false, true, false); + } + + public void testFileRegionVoidPromise(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFileRegion0(sb, cb, true, true, true); + } + + public void testFileRegionNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFileRegion0(sb, cb, false, false, true); + } + + public void testFileRegionVoidPromiseNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFileRegion0(sb, cb, true, false, true); + } + + public void testFileRegionCountLargerThenFile(ServerBootstrap sb, Bootstrap cb) throws Throwable { + File file = PlatformDependent.createTempFile("netty-", ".tmp", null); + file.deleteOnExit(); + + final FileOutputStream out = new FileOutputStream(file); + out.write(data); + out.close(); + + sb.childHandler(new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + // Just drop the message. + } + }); + cb.handler(new ChannelInboundHandlerAdapter()); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + // Request file region which is bigger then the underlying file. + FileRegion region = new DefaultFileRegion( + new RandomAccessFile(file, "r").getChannel(), 0, data.length + 1024); + + assertThat(cc.writeAndFlush(region).await().cause(), CoreMatchers.instanceOf(IOException.class)); + cc.close().sync(); + sc.close().sync(); + } + + private static void testFileRegion0( + ServerBootstrap sb, Bootstrap cb, boolean voidPromise, final boolean autoRead, boolean defaultFileRegion) + throws Throwable { + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + final int bufferSize = 1024; + final File file = PlatformDependent.createTempFile("netty-", ".tmp", null); + file.deleteOnExit(); + + final FileOutputStream out = new FileOutputStream(file); + final Random random = PlatformDependent.threadLocalRandom(); + + // Prepend random data which will not be transferred, so that we can test non-zero start offset + final int startOffset = random.nextInt(8192); + for (int i = 0; i < startOffset; i ++) { + out.write(random.nextInt()); + } + + // .. and here comes the real data to transfer. + out.write(data, bufferSize, data.length - bufferSize); + + // .. and then some extra data which is not supposed to be transferred. + for (int i = random.nextInt(8192); i > 0; i --) { + out.write(random.nextInt()); + } + + out.close(); + + ChannelInboundHandler ch = new SimpleChannelInboundHandler() { + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.close(); + } + }; + TestHandler sh = new TestHandler(autoRead); + + sb.childHandler(sh); + cb.handler(ch); + + Channel sc = sb.bind().sync().channel(); + + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + FileRegion region = new DefaultFileRegion( + new RandomAccessFile(file, "r").getChannel(), startOffset, data.length - bufferSize); + FileRegion emptyRegion = new DefaultFileRegion(new RandomAccessFile(file, "r").getChannel(), 0, 0); + + if (!defaultFileRegion) { + region = new FileRegionWrapper(region); + emptyRegion = new FileRegionWrapper(emptyRegion); + } + // Do write ByteBuf and then FileRegion to ensure that mixed writes work + // Also, write an empty FileRegion to test if writing an empty FileRegion does not cause any issues. + // + // See https://github.com/netty/netty/issues/2769 + // https://github.com/netty/netty/issues/2964 + if (voidPromise) { + assertEquals(cc.voidPromise(), cc.write(Unpooled.wrappedBuffer(data, 0, bufferSize), cc.voidPromise())); + assertEquals(cc.voidPromise(), cc.write(emptyRegion, cc.voidPromise())); + assertEquals(cc.voidPromise(), cc.writeAndFlush(region, cc.voidPromise())); + } else { + assertNotEquals(cc.voidPromise(), cc.write(Unpooled.wrappedBuffer(data, 0, bufferSize))); + assertNotEquals(cc.voidPromise(), cc.write(emptyRegion)); + assertNotEquals(cc.voidPromise(), cc.writeAndFlush(region)); + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().sync(); + cc.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + + // Make sure we did not receive more than we expected. + assertThat(sh.counter, is(data.length)); + } + + private static class TestHandler extends SimpleChannelInboundHandler { + private final boolean autoRead; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + TestHandler(boolean autoRead) { + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + counter += actual.length; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } + + private static final class FileRegionWrapper implements FileRegion { + private final FileRegion region; + + FileRegionWrapper(FileRegion region) { + this.region = region; + } + + @Override + public int refCnt() { + return region.refCnt(); + } + + @Override + public long position() { + return region.position(); + } + + @Override + @Deprecated + public long transfered() { + return region.transferred(); + } + + @Override + public boolean release() { + return region.release(); + } + + @Override + public long transferred() { + return region.transferred(); + } + + @Override + public long count() { + return region.count(); + } + + @Override + public boolean release(int decrement) { + return region.release(decrement); + } + + @Override + public long transferTo(WritableByteChannel target, long position) throws IOException { + return region.transferTo(target, position); + } + + @Override + public FileRegion retain() { + region.retain(); + return this; + } + + @Override + public FileRegion retain(int increment) { + region.retain(increment); + return this; + } + + @Override + public FileRegion touch() { + region.touch(); + return this; + } + + @Override + public FileRegion touch(Object hint) { + region.touch(hint); + return this; + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFixedLengthEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFixedLengthEchoTest.java new file mode 100644 index 0000000..7051683 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFixedLengthEchoTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.FixedLengthFrameDecoder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SocketFixedLengthEchoTest extends AbstractSocketTest { + + private static final Random random = new Random(); + static final byte[] data = new byte[1048576]; + + static { + random.nextBytes(data); + } + + @Test + public void testFixedLengthEcho(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFixedLengthEcho(serverBootstrap, bootstrap); + } + }); + } + + @Test + public void testFixedLengthEchoNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testFixedLengthEchoNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testFixedLengthEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFixedLengthEcho(sb, cb, true); + } + + public void testFixedLengthEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testFixedLengthEcho(sb, cb, false); + } + + private static void testFixedLengthEcho(ServerBootstrap sb, Bootstrap cb, boolean autoRead) throws Throwable { + final EchoHandler sh = new EchoHandler(autoRead); + final EchoHandler ch = new EchoHandler(autoRead); + + sb.childOption(ChannelOption.AUTO_READ, autoRead); + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + sch.pipeline().addLast("decoder", new FixedLengthFrameDecoder(1024)); + sch.pipeline().addAfter("decoder", "handler", sh); + } + }); + + cb.option(ChannelOption.AUTO_READ, autoRead); + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + sch.pipeline().addLast("decoder", new FixedLengthFrameDecoder(1024)); + sch.pipeline().addAfter("decoder", "handler", ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 3), data.length - i); + cc.writeAndFlush(Unpooled.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends SimpleChannelInboundHandler { + private final boolean autoRead; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler(boolean autoRead) { + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + assertEquals(1024, msg.readableBytes()); + + byte[] actual = new byte[msg.readableBytes()]; + msg.getBytes(0, actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.parent() != null) { + channel.write(msg.retain()); + } + + counter += actual.length; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + try { + ctx.flush(); + } finally { + if (!autoRead) { + ctx.read(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketGatheringWriteTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketGatheringWriteTest.java new file mode 100644 index 0000000..dc74cd1 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketGatheringWriteTest.java @@ -0,0 +1,278 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.testsuite.util.TestUtils; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.StringUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static io.netty.buffer.Unpooled.compositeBuffer; +import static io.netty.buffer.Unpooled.wrappedBuffer; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketGatheringWriteTest extends AbstractSocketTest { + private static final long TIMEOUT = 120000; + + private static final Random random = new Random(); + static final byte[] data = new byte[1048576]; + + static { + random.nextBytes(data); + } + + @AfterAll + public static void compressHeapDumps() throws Exception { + TestUtils.compressHeapDumps(); + } + + @Test + @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS) + public void testGatheringWrite(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testGatheringWrite(serverBootstrap, bootstrap); + } + }); + } + + public void testGatheringWrite(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testGatheringWrite0(sb, cb, data, false, true); + } + + @Test + @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS) + public void testGatheringWriteNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testGatheringWriteNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testGatheringWriteNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testGatheringWrite0(sb, cb, data, false, false); + } + + @Test + @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS) + public void testGatheringWriteWithComposite(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testGatheringWriteWithComposite(serverBootstrap, bootstrap); + } + }); + } + + public void testGatheringWriteWithComposite(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testGatheringWrite0(sb, cb, data, true, true); + } + + @Test + @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS) + public void testGatheringWriteWithCompositeNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testGatheringWriteWithCompositeNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testGatheringWriteWithCompositeNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testGatheringWrite0(sb, cb, data, true, false); + } + + // Test for https://github.com/netty/netty/issues/2647 + @Test + @Timeout(value = TIMEOUT, unit = TimeUnit.MILLISECONDS) + public void testGatheringWriteBig(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testGatheringWriteBig(serverBootstrap, bootstrap); + } + }); + } + + public void testGatheringWriteBig(ServerBootstrap sb, Bootstrap cb) throws Throwable { + byte[] bigData = new byte[1024 * 1024 * 50]; + random.nextBytes(bigData); + testGatheringWrite0(sb, cb, bigData, false, true); + } + + private void testGatheringWrite0( + ServerBootstrap sb, Bootstrap cb, byte[] data, boolean composite, boolean autoRead) throws Throwable { + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + Promise serverDonePromise = ImmediateEventExecutor.INSTANCE.newPromise(); + final TestServerHandler sh = new TestServerHandler(autoRead, serverDonePromise, data.length); + final TestHandler ch = new TestHandler(autoRead); + + cb.handler(ch); + sb.childHandler(sh); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + for (int i = 0; i < data.length;) { + int length = Math.min(random.nextInt(1024 * 8), data.length - i); + if (composite && i % 2 == 0) { + int firstBufLength = length / 2; + CompositeByteBuf comp = compositeBuffer(); + comp.addComponent(true, wrappedBuffer(data, i, firstBufLength)) + .addComponent(true, wrappedBuffer(data, i + firstBufLength, length - firstBufLength)); + cc.write(comp); + } else { + cc.write(wrappedBuffer(data, i, length)); + } + i += length; + } + + ChannelFuture cf = cc.writeAndFlush(Unpooled.EMPTY_BUFFER); + assertNotEquals(cc.voidPromise(), cf); + try { + assertTrue(cf.await(60000)); + cf.sync(); + } catch (Throwable t) { + // TODO: Remove this once we fix this test. + TestUtils.dump(StringUtil.simpleClassName(this)); + throw t; + } + + serverDonePromise.sync(); + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + ByteBuf expected = wrappedBuffer(data); + assertEquals(expected, sh.received); + expected.release(); + sh.received.release(); + } + + private static final class TestServerHandler extends TestHandler { + private final int expectedBytes; + private final Promise doneReadingPromise; + final ByteBuf received = Unpooled.buffer(); + + TestServerHandler(boolean autoRead, Promise doneReadingPromise, int expectedBytes) { + super(autoRead); + this.doneReadingPromise = doneReadingPromise; + this.expectedBytes = expectedBytes; + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + received.writeBytes(in); + if (received.readableBytes() >= expectedBytes) { + doneReadingPromise.setSuccess(null); + } + } + + @Override + void handleException(ChannelHandlerContext ctx, Throwable cause) { + doneReadingPromise.tryFailure(cause); + super.handleException(ctx, cause); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + doneReadingPromise.tryFailure(new IllegalStateException("server closed!")); + super.channelInactive(ctx); + } + } + + private static class TestHandler extends SimpleChannelInboundHandler { + private final boolean autoRead; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + + TestHandler(boolean autoRead) { + this.autoRead = autoRead; + } + + @Override + public final void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + super.channelActive(ctx); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + } + + @Override + public final void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + super.channelReadComplete(ctx); + } + + @Override + public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + handleException(ctx, cause); + } + super.exceptionCaught(ctx, cause); + } + + void handleException(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketHalfClosedTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketHalfClosedTest.java new file mode 100644 index 0000000..28a882c --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketHalfClosedTest.java @@ -0,0 +1,740 @@ +/* + * Copyright 2017 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.ChannelInputShutdownEvent; +import io.netty.channel.socket.ChannelInputShutdownReadComplete; +import io.netty.channel.socket.ChannelOutputShutdownEvent; +import io.netty.channel.socket.DuplexChannel; +import io.netty.channel.socket.SocketChannel; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.UncheckedBooleanSupplier; +import io.netty.util.internal.PlatformDependent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +public class SocketHalfClosedTest extends AbstractSocketTest { + + protected int maxReadCompleteWithNoDataAfterInputShutdown() { + return 2; // nio needs read flag to detect full closure. + } + + @Test + @Timeout(value = 5000, unit = MILLISECONDS) + public void testHalfClosureReceiveDataOnFinalWait2StateWhenSoLingerSet(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testHalfClosureReceiveDataOnFinalWait2StateWhenSoLingerSet(serverBootstrap, bootstrap); + } + }); + } + + private void testHalfClosureReceiveDataOnFinalWait2StateWhenSoLingerSet(ServerBootstrap sb, Bootstrap cb) + throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + + final CountDownLatch waitHalfClosureDone = new CountDownLatch(1); + try { + sb.childOption(ChannelOption.SO_LINGER, 1) + .childHandler(new ChannelInitializer() { + + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + + @Override + public void channelActive(final ChannelHandlerContext ctx) { + SocketChannel channel = (SocketChannel) ctx.channel(); + channel.shutdownOutput(); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ReferenceCountUtil.release(msg); + waitHalfClosureDone.countDown(); + } + }); + } + }); + + cb.option(ChannelOption.ALLOW_HALF_CLOSURE, true) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (ChannelInputShutdownEvent.INSTANCE == evt) { + ctx.writeAndFlush(ctx.alloc().buffer().writeZero(16)); + } + + if (ChannelInputShutdownReadComplete.INSTANCE == evt) { + ctx.close(); + } + } + }); + } + }); + + serverChannel = sb.bind().sync().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + waitHalfClosureDone.await(); + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + @Test + @Timeout(value = 10000, unit = MILLISECONDS) + public void testHalfClosureOnlyOneEventWhenAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testHalfClosureOnlyOneEventWhenAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testHalfClosureOnlyOneEventWhenAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + try { + cb.option(ChannelOption.ALLOW_HALF_CLOSURE, true) + .option(ChannelOption.AUTO_READ, true); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) { + ((DuplexChannel) ctx).shutdownOutput(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + }); + } + }); + + final AtomicInteger shutdownEventReceivedCounter = new AtomicInteger(); + final AtomicInteger shutdownReadCompleteEventReceivedCounter = new AtomicInteger(); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + + @Override + public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) { + if (evt == ChannelInputShutdownEvent.INSTANCE) { + shutdownEventReceivedCounter.incrementAndGet(); + } else if (evt == ChannelInputShutdownReadComplete.INSTANCE) { + shutdownReadCompleteEventReceivedCounter.incrementAndGet(); + ctx.executor().schedule(new Runnable() { + @Override + public void run() { + ctx.close(); + } + }, 100, MILLISECONDS); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + }); + } + }); + + serverChannel = sb.bind().sync().channel(); + Channel clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + clientChannel.closeFuture().await(); + assertEquals(1, shutdownEventReceivedCounter.get()); + assertEquals(1, shutdownReadCompleteEventReceivedCounter.get()); + } finally { + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + @Test + public void testAllDataReadAfterHalfClosure(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAllDataReadAfterHalfClosure(serverBootstrap, bootstrap); + } + }); + } + + public void testAllDataReadAfterHalfClosure(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testAllDataReadAfterHalfClosure(true, sb, cb); + testAllDataReadAfterHalfClosure(false, sb, cb); + } + + private void testAllDataReadAfterHalfClosure(final boolean autoRead, + ServerBootstrap sb, Bootstrap cb) throws Throwable { + final int totalServerBytesWritten = 1024 * 16; + final int numReadsPerReadLoop = 2; + final CountDownLatch serverInitializedLatch = new CountDownLatch(1); + final CountDownLatch clientReadAllDataLatch = new CountDownLatch(1); + final CountDownLatch clientHalfClosedLatch = new CountDownLatch(1); + final AtomicInteger clientReadCompletes = new AtomicInteger(); + final AtomicInteger clientZeroDataReadCompletes = new AtomicInteger(); + Channel serverChannel = null; + Channel clientChannel = null; + try { + cb.option(ChannelOption.ALLOW_HALF_CLOSURE, true) + .option(ChannelOption.AUTO_READ, autoRead) + .option(ChannelOption.RCVBUF_ALLOCATOR, new TestNumReadsRecvByteBufAllocator(numReadsPerReadLoop)); + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ByteBuf buf = ctx.alloc().buffer(totalServerBytesWritten); + buf.writerIndex(buf.capacity()); + ctx.writeAndFlush(buf).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + ((DuplexChannel) future.channel()).shutdownOutput(); + } + }); + serverInitializedLatch.countDown(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + }); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + private int bytesRead; + private int bytesSinceReadComplete; + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ByteBuf buf = (ByteBuf) msg; + bytesRead += buf.readableBytes(); + bytesSinceReadComplete += buf.readableBytes(); + buf.release(); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt == ChannelInputShutdownEvent.INSTANCE) { + clientHalfClosedLatch.countDown(); + } else if (evt == ChannelInputShutdownReadComplete.INSTANCE) { + ctx.close(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + if (bytesSinceReadComplete == 0) { + clientZeroDataReadCompletes.incrementAndGet(); + } else { + bytesSinceReadComplete = 0; + } + clientReadCompletes.incrementAndGet(); + if (bytesRead == totalServerBytesWritten) { + clientReadAllDataLatch.countDown(); + } + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + }); + } + }); + + serverChannel = sb.bind().sync().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + clientChannel.read(); + + serverInitializedLatch.await(); + clientReadAllDataLatch.await(); + clientHalfClosedLatch.await(); + // In practice this should be much less, as we allow numReadsPerReadLoop per wakeup, but we limit the + // number of bytes to 1 per read so in theory we may need more. We check below that readComplete is called + // when data is actually read. + assertTrue(totalServerBytesWritten > clientReadCompletes.get(), + "too many read complete events: " + clientReadCompletes.get()); + assertTrue(clientZeroDataReadCompletes.get() <= maxReadCompleteWithNoDataAfterInputShutdown(), + "too many readComplete with no data: " + clientZeroDataReadCompletes.get() + " readComplete: " + + clientReadCompletes.get()); + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + @Test + public void testAutoCloseFalseDoesShutdownOutput(TestInfo testInfo) throws Throwable { + // This test only works on Linux / BSD / MacOS as we assume some semantics that are not true for Windows. + assumeFalse(PlatformDependent.isWindows()); + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAutoCloseFalseDoesShutdownOutput(serverBootstrap, bootstrap); + } + }); + } + + public void testAutoCloseFalseDoesShutdownOutput(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testAutoCloseFalseDoesShutdownOutput(false, false, sb, cb); + testAutoCloseFalseDoesShutdownOutput(false, true, sb, cb); + testAutoCloseFalseDoesShutdownOutput(true, false, sb, cb); + testAutoCloseFalseDoesShutdownOutput(true, true, sb, cb); + } + + private static void testAutoCloseFalseDoesShutdownOutput(boolean allowHalfClosed, + final boolean clientIsLeader, + ServerBootstrap sb, + Bootstrap cb) throws InterruptedException { + final int expectedBytes = 100; + final CountDownLatch serverReadExpectedLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(2); + final AtomicReference causeRef = new AtomicReference(); + Channel serverChannel = null; + Channel clientChannel = null; + try { + cb.option(ChannelOption.ALLOW_HALF_CLOSURE, allowHalfClosed) + .option(ChannelOption.AUTO_CLOSE, false) + .option(ChannelOption.SO_LINGER, 0); + sb.childOption(ChannelOption.ALLOW_HALF_CLOSURE, allowHalfClosed) + .childOption(ChannelOption.AUTO_CLOSE, false) + .childOption(ChannelOption.SO_LINGER, 0); + + final AutoCloseFalseLeader leaderHandler = new AutoCloseFalseLeader(expectedBytes, + serverReadExpectedLatch, doneLatch, causeRef); + final AutoCloseFalseFollower followerHandler = new AutoCloseFalseFollower(expectedBytes, + serverReadExpectedLatch, doneLatch, causeRef); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(clientIsLeader ? followerHandler :leaderHandler); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(clientIsLeader ? leaderHandler : followerHandler); + } + }); + + serverChannel = sb.bind().sync().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + + doneLatch.await(); + assertNull(causeRef.get()); + assertTrue(leaderHandler.seenOutputShutdown); + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + private static final class AutoCloseFalseFollower extends SimpleChannelInboundHandler { + private final int expectedBytes; + private final CountDownLatch followerCloseLatch; + private final CountDownLatch doneLatch; + private final AtomicReference causeRef; + private int bytesRead; + + AutoCloseFalseFollower(int expectedBytes, CountDownLatch followerCloseLatch, CountDownLatch doneLatch, + AtomicReference causeRef) { + this.expectedBytes = expectedBytes; + this.followerCloseLatch = followerCloseLatch; + this.doneLatch = doneLatch; + this.causeRef = causeRef; + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + checkPrematureClose(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + checkPrematureClose(); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + bytesRead += msg.readableBytes(); + if (bytesRead >= expectedBytes) { + // We write a reply and immediately close our end of the socket. + ByteBuf buf = ctx.alloc().buffer(expectedBytes); + buf.writerIndex(buf.writerIndex() + expectedBytes); + ctx.writeAndFlush(buf).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + future.channel().close().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(final ChannelFuture future) throws Exception { + // This is a bit racy but there is no better way how to handle this in Java11. + // The problem is that on close() the underlying FD will not actually be closed directly + // but the close will be done after the Selector did process all events. Because of + // this we will need to give it a bit time to ensure the FD is actual closed before we + // count down the latch and try to write. + future.channel().eventLoop().schedule(new Runnable() { + @Override + public void run() { + followerCloseLatch.countDown(); + } + }, 200, TimeUnit.MILLISECONDS); + } + }); + } + }); + } + } + + private void checkPrematureClose() { + if (bytesRead < expectedBytes) { + causeRef.set(new IllegalStateException("follower premature close")); + doneLatch.countDown(); + } + } + } + + private static final class AutoCloseFalseLeader extends SimpleChannelInboundHandler { + private final int expectedBytes; + private final CountDownLatch followerCloseLatch; + private final CountDownLatch doneLatch; + private final AtomicReference causeRef; + private int bytesRead; + boolean seenOutputShutdown; + + AutoCloseFalseLeader(int expectedBytes, CountDownLatch followerCloseLatch, CountDownLatch doneLatch, + AtomicReference causeRef) { + this.expectedBytes = expectedBytes; + this.followerCloseLatch = followerCloseLatch; + this.doneLatch = doneLatch; + this.causeRef = causeRef; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ByteBuf buf = ctx.alloc().buffer(expectedBytes); + buf.writerIndex(buf.writerIndex() + expectedBytes); + ctx.writeAndFlush(buf.retainedDuplicate()); + + // We wait here to ensure that we write before we have a chance to process the outbound + // shutdown event. + followerCloseLatch.await(); + + // This write should fail, but we should still be allowed to read the peer's data + ctx.writeAndFlush(buf).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.cause() == null) { + causeRef.set(new IllegalStateException("second write should have failed!")); + doneLatch.countDown(); + } + } + }); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + bytesRead += msg.readableBytes(); + if (bytesRead >= expectedBytes) { + doneLatch.countDown(); + } + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof ChannelOutputShutdownEvent) { + seenOutputShutdown = true; + doneLatch.countDown(); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + checkPrematureClose(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + checkPrematureClose(); + } + + private void checkPrematureClose() { + if (bytesRead < expectedBytes || !seenOutputShutdown) { + causeRef.set(new IllegalStateException("leader premature close")); + doneLatch.countDown(); + } + } + } + + @Test + public void testAllDataReadClosure(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAllDataReadClosure(serverBootstrap, bootstrap); + } + }); + } + + public void testAllDataReadClosure(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testAllDataReadClosure(true, false, sb, cb); + testAllDataReadClosure(true, true, sb, cb); + testAllDataReadClosure(false, false, sb, cb); + testAllDataReadClosure(false, true, sb, cb); + } + + private static void testAllDataReadClosure(final boolean autoRead, final boolean allowHalfClosed, + ServerBootstrap sb, Bootstrap cb) throws Throwable { + final int totalServerBytesWritten = 1024 * 16; + final int numReadsPerReadLoop = 2; + final CountDownLatch serverInitializedLatch = new CountDownLatch(1); + final CountDownLatch clientReadAllDataLatch = new CountDownLatch(1); + final CountDownLatch clientHalfClosedLatch = new CountDownLatch(1); + final AtomicInteger clientReadCompletes = new AtomicInteger(); + Channel serverChannel = null; + Channel clientChannel = null; + try { + cb.option(ChannelOption.ALLOW_HALF_CLOSURE, allowHalfClosed) + .option(ChannelOption.AUTO_READ, autoRead) + .option(ChannelOption.RCVBUF_ALLOCATOR, new TestNumReadsRecvByteBufAllocator(numReadsPerReadLoop)); + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ByteBuf buf = ctx.alloc().buffer(totalServerBytesWritten); + buf.writerIndex(buf.capacity()); + ctx.writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); + serverInitializedLatch.countDown(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + }); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + private int bytesRead; + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + ByteBuf buf = (ByteBuf) msg; + bytesRead += buf.readableBytes(); + buf.release(); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt == ChannelInputShutdownEvent.INSTANCE && allowHalfClosed) { + clientHalfClosedLatch.countDown(); + } else if (evt == ChannelInputShutdownReadComplete.INSTANCE) { + ctx.close(); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + if (!allowHalfClosed) { + clientHalfClosedLatch.countDown(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + clientReadCompletes.incrementAndGet(); + if (bytesRead == totalServerBytesWritten) { + clientReadAllDataLatch.countDown(); + } + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + ctx.close(); + } + }); + } + }); + + serverChannel = sb.bind().sync().channel(); + clientChannel = cb.connect(serverChannel.localAddress()).sync().channel(); + clientChannel.read(); + + serverInitializedLatch.await(); + clientReadAllDataLatch.await(); + clientHalfClosedLatch.await(); + assertTrue(totalServerBytesWritten / numReadsPerReadLoop + 10 > clientReadCompletes.get(), + "too many read complete events: " + clientReadCompletes.get()); + } finally { + if (clientChannel != null) { + clientChannel.close().sync(); + } + if (serverChannel != null) { + serverChannel.close().sync(); + } + } + } + + /** + * Designed to read a single byte at a time to control the number of reads done at a fine granularity. + */ + private static final class TestNumReadsRecvByteBufAllocator implements RecvByteBufAllocator { + private final int numReads; + TestNumReadsRecvByteBufAllocator(int numReads) { + this.numReads = numReads; + } + + @Override + public ExtendedHandle newHandle() { + return new ExtendedHandle() { + private int attemptedBytesRead; + private int lastBytesRead; + private int numMessagesRead; + @Override + public ByteBuf allocate(ByteBufAllocator alloc) { + return alloc.ioBuffer(guess(), guess()); + } + + @Override + public int guess() { + return 1; // only ever allocate buffers of size 1 to ensure the number of reads is controlled. + } + + @Override + public void reset(ChannelConfig config) { + numMessagesRead = 0; + } + + @Override + public void incMessagesRead(int numMessages) { + numMessagesRead += numMessages; + } + + @Override + public void lastBytesRead(int bytes) { + lastBytesRead = bytes; + } + + @Override + public int lastBytesRead() { + return lastBytesRead; + } + + @Override + public void attemptedBytesRead(int bytes) { + attemptedBytesRead = bytes; + } + + @Override + public int attemptedBytesRead() { + return attemptedBytesRead; + } + + @Override + public boolean continueReading() { + return numMessagesRead < numReads; + } + + @Override + public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) { + return continueReading() && maybeMoreDataSupplier.get(); + } + + @Override + public void readComplete() { + // Nothing needs to be done or adjusted after each read cycle is completed. + } + }; + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketMultipleConnectTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketMultipleConnectTest.java new file mode 100644 index 0000000..3019e6b --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketMultipleConnectTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.testsuite.transport.TestsuitePermutation; +import io.netty.util.NetUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.nio.channels.AlreadyConnectedException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketMultipleConnectTest extends AbstractSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testMultipleConnect(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testMultipleConnect(serverBootstrap, bootstrap); + } + }); + } + + public void testMultipleConnect(ServerBootstrap sb, Bootstrap cb) throws Exception { + Channel sc = null; + Channel cc = null; + try { + sb.childHandler(new ChannelInboundHandlerAdapter()); + sc = sb.bind(NetUtil.LOCALHOST, 0).syncUninterruptibly().channel(); + + cb.handler(new ChannelInboundHandlerAdapter()); + cc = cb.register().syncUninterruptibly().channel(); + cc.connect(sc.localAddress()).syncUninterruptibly(); + ChannelFuture connectFuture2 = cc.connect(sc.localAddress()).await(); + assertTrue(connectFuture2.cause() instanceof AlreadyConnectedException); + } finally { + if (cc != null) { + cc.close(); + } + if (sc != null) { + sc.close(); + } + } + } + + @Override + protected List> newFactories() { + List> factories + = new ArrayList>(); + for (TestsuitePermutation.BootstrapComboFactory comboFactory + : SocketTestPermutation.INSTANCE.socketWithFastOpen()) { + if (comboFactory.newClientInstance().config().group() instanceof NioEventLoopGroup) { + factories.add(comboFactory); + } + } + return factories; + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketObjectEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketObjectEchoTest.java new file mode 100644 index 0000000..7145904 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketObjectEchoTest.java @@ -0,0 +1,204 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.handler.codec.serialization.ClassResolvers; +import io.netty.handler.codec.serialization.ObjectDecoder; +import io.netty.handler.codec.serialization.ObjectEncoder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SocketObjectEchoTest extends AbstractSocketTest { + + static final Random random = new Random(); + static final String[] data = new String[1024]; + + static { + for (int i = 0; i < data.length; i ++) { + int eLen = random.nextInt(512); + char[] e = new char[eLen]; + for (int j = 0; j < eLen; j ++) { + e[j] = (char) ('a' + random.nextInt(26)); + } + + data[i] = new String(e); + } + } + + @Test + public void testObjectEcho(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testObjectEcho(serverBootstrap, bootstrap); + } + }); + } + + public void testObjectEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testObjectEcho(sb, cb, true); + } + + @Test + public void testObjectEchoNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testObjectEchoNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testObjectEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testObjectEcho(sb, cb, false); + } + + private static void testObjectEcho(ServerBootstrap sb, Bootstrap cb, boolean autoRead) throws Throwable { + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + final EchoHandler sh = new EchoHandler(autoRead); + final EchoHandler ch = new EchoHandler(autoRead); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + sch.pipeline().addLast( + new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())), + new ObjectEncoder(), + sh); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + sch.pipeline().addLast( + new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())), + new ObjectEncoder(), + ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + for (String element : data) { + cc.writeAndFlush(element); + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends ChannelInboundHandlerAdapter { + private final boolean autoRead; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler(boolean autoRead) { + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + assertEquals(data[counter], msg); + + if (channel.parent() != null) { + channel.write(msg); + } + + counter ++; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + try { + ctx.flush(); + } finally { + if (!autoRead) { + ctx.read(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketReadPendingTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketReadPendingTest.java new file mode 100644 index 0000000..ac97872 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketReadPendingTest.java @@ -0,0 +1,212 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.RecvByteBufAllocator; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.UncheckedBooleanSupplier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketReadPendingTest extends AbstractSocketTest { + @Test + @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + public void testReadPendingIsResetAfterEachRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testReadPendingIsResetAfterEachRead(serverBootstrap, bootstrap); + } + }); + } + + public void testReadPendingIsResetAfterEachRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + Channel serverChannel = null; + Channel clientChannel = null; + try { + ReadPendingInitializer serverInitializer = new ReadPendingInitializer(); + ReadPendingInitializer clientInitializer = new ReadPendingInitializer(); + sb.option(ChannelOption.SO_BACKLOG, 1024) + .option(ChannelOption.AUTO_READ, true) + .childOption(ChannelOption.AUTO_READ, false) + // We intend to do 2 reads per read loop wakeup + .childOption(ChannelOption.RCVBUF_ALLOCATOR, new TestNumReadsRecvByteBufAllocator(2)) + .childHandler(serverInitializer); + + serverChannel = sb.bind().syncUninterruptibly().channel(); + + cb.option(ChannelOption.AUTO_READ, false) + // We intend to do 2 reads per read loop wakeup + .option(ChannelOption.RCVBUF_ALLOCATOR, new TestNumReadsRecvByteBufAllocator(2)) + .handler(clientInitializer); + clientChannel = cb.connect(serverChannel.localAddress()).syncUninterruptibly().channel(); + + // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator + clientChannel.writeAndFlush(Unpooled.wrappedBuffer(new byte[4])); + + // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator + assertTrue(serverInitializer.channelInitLatch.await(5, TimeUnit.SECONDS)); + serverInitializer.channel.writeAndFlush(Unpooled.wrappedBuffer(new byte[4])); + + serverInitializer.channel.read(); + serverInitializer.readPendingHandler.assertAllRead(); + + clientChannel.read(); + clientInitializer.readPendingHandler.assertAllRead(); + } finally { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + if (clientChannel != null) { + clientChannel.close().syncUninterruptibly(); + } + } + } + + private static class ReadPendingInitializer extends ChannelInitializer { + final ReadPendingReadHandler readPendingHandler = new ReadPendingReadHandler(); + final CountDownLatch channelInitLatch = new CountDownLatch(1); + volatile Channel channel; + + @Override + protected void initChannel(Channel ch) throws Exception { + channel = ch; + ch.pipeline().addLast(readPendingHandler); + channelInitLatch.countDown(); + } + } + + private static final class ReadPendingReadHandler extends ChannelInboundHandlerAdapter { + private final AtomicInteger count = new AtomicInteger(); + private final CountDownLatch latch = new CountDownLatch(1); + private final CountDownLatch latch2 = new CountDownLatch(2); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ReferenceCountUtil.release(msg); + if (count.incrementAndGet() == 1) { + // Call read the first time, to ensure it is not reset the second time. + ctx.read(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + latch.countDown(); + latch2.countDown(); + } + + void assertAllRead() throws InterruptedException { + assertTrue(latch.await(5, TimeUnit.SECONDS)); + // We should only do 1 read loop, because we only called read() on the first channelRead. + assertFalse(latch2.await(1, TimeUnit.SECONDS)); + assertEquals(2, count.get()); + } + } + + /** + * Designed to read a single byte at a time to control the number of reads done at a fine granularity. + */ + private static final class TestNumReadsRecvByteBufAllocator implements RecvByteBufAllocator { + private final int numReads; + TestNumReadsRecvByteBufAllocator(int numReads) { + this.numReads = numReads; + } + + @Override + public ExtendedHandle newHandle() { + return new ExtendedHandle() { + private int attemptedBytesRead; + private int lastBytesRead; + private int numMessagesRead; + @Override + public ByteBuf allocate(ByteBufAllocator alloc) { + return alloc.ioBuffer(guess(), guess()); + } + + @Override + public int guess() { + return 1; // only ever allocate buffers of size 1 to ensure the number of reads is controlled. + } + + @Override + public void reset(ChannelConfig config) { + numMessagesRead = 0; + } + + @Override + public void incMessagesRead(int numMessages) { + numMessagesRead += numMessages; + } + + @Override + public void lastBytesRead(int bytes) { + lastBytesRead = bytes; + } + + @Override + public int lastBytesRead() { + return lastBytesRead; + } + + @Override + public void attemptedBytesRead(int bytes) { + attemptedBytesRead = bytes; + } + + @Override + public int attemptedBytesRead() { + return attemptedBytesRead; + } + + @Override + public boolean continueReading() { + return numMessagesRead < numReads; + } + + @Override + public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) { + return continueReading(); + } + + @Override + public void readComplete() { + // Nothing needs to be done or adjusted after each read cycle is completed. + } + }; + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketRstTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketRstTest.java new file mode 100644 index 0000000..ab66880 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketRstTest.java @@ -0,0 +1,161 @@ +/* + * Copyright 2016 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.Locale; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketRstTest extends AbstractSocketTest { + protected void assertRstOnCloseException(IOException cause, Channel clientChannel) { + if (Locale.getDefault() == Locale.US || Locale.getDefault() == Locale.UK) { + assertTrue(cause.getMessage().contains("reset") || cause.getMessage().contains("closed"), + "actual message: " + cause.getMessage()); + } + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testSoLingerZeroCausesOnlyRstOnClose(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSoLingerZeroCausesOnlyRstOnClose(serverBootstrap, bootstrap); + } + }); + } + + public void testSoLingerZeroCausesOnlyRstOnClose(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final AtomicReference serverChannelRef = new AtomicReference(); + final AtomicReference throwableRef = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + // SO_LINGER=0 means that we must send ONLY a RST when closing (not a FIN + RST). + sb.childOption(ChannelOption.SO_LINGER, 0); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + serverChannelRef.compareAndSet(null, ch); + latch.countDown(); + } + }); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + throwableRef.compareAndSet(null, cause); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + latch2.countDown(); + } + }); + } + }); + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + // Wait for the server to get setup. + latch.await(); + + // The server has SO_LINGER=0 and so it must send a RST when close is called. + serverChannelRef.get().close(); + + // Wait for the client to get channelInactive. + latch2.await(); + + // Verify the client received a RST. + Throwable cause = throwableRef.get(); + assertTrue(cause instanceof IOException, + "actual [type, message]: [" + cause.getClass() + ", " + cause.getMessage() + "]"); + + assertRstOnCloseException((IOException) cause, cc); + } + + @Test + @Timeout(value = 3000, unit = TimeUnit.MILLISECONDS) + public void testNoRstIfSoLingerOnClose(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testNoRstIfSoLingerOnClose(serverBootstrap, bootstrap); + } + }); + } + + public void testNoRstIfSoLingerOnClose(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final AtomicReference serverChannelRef = new AtomicReference(); + final AtomicReference throwableRef = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + serverChannelRef.compareAndSet(null, ch); + latch.countDown(); + } + }); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + throwableRef.compareAndSet(null, cause); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + latch2.countDown(); + } + }); + } + }); + Channel sc = sb.bind().sync().channel(); + cb.connect(sc.localAddress()).syncUninterruptibly(); + + // Wait for the server to get setup. + latch.await(); + + // The server has SO_LINGER=0 and so it must send a RST when close is called. + serverChannelRef.get().close(); + + // Wait for the client to get channelInactive. + latch2.await(); + + // Verify the client did not received a RST. + assertNull(throwableRef.get()); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputByPeerTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputByPeerTest.java new file mode 100644 index 0000000..9ef58c4 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputByPeerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.util.internal.SocketUtils; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketAddress; + +public class SocketShutdownOutputByPeerTest extends AbstractSocketShutdownOutputByPeerTest { + + @Override + protected void shutdownOutput(Socket s) throws IOException { + s.shutdownOutput(); + } + + @Override + protected void connect(Socket s, SocketAddress address) throws IOException { + SocketUtils.connect(s, address, 10000); + } + + @Override + protected void close(Socket s) throws IOException { + s.close(); + } + + @Override + protected void write(Socket s, int data) throws IOException { + s.getOutputStream().write(data); + } + + @Override + protected Socket newSocket() { + return new Socket(); + } + +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java new file mode 100644 index 0000000..ab8a5df --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketShutdownOutputBySelfTest.java @@ -0,0 +1,321 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.oio.OioSocketChannel; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.channels.ClosedChannelException; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +public class SocketShutdownOutputBySelfTest extends AbstractClientSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testShutdownOutput(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testShutdownOutput(bootstrap); + } + }); + } + + public void testShutdownOutput(Bootstrap cb) throws Throwable { + TestHandler h = new TestHandler(); + ServerSocket ss = new ServerSocket(); + Socket s = null; + SocketChannel ch = null; + try { + ss.bind(newSocketAddress()); + ch = (SocketChannel) cb.handler(h).connect(ss.getLocalSocketAddress()).sync().channel(); + assertTrue(ch.isActive()); + assertFalse(ch.isOutputShutdown()); + + s = ss.accept(); + ch.writeAndFlush(Unpooled.wrappedBuffer(new byte[] { 1 })).sync(); + assertEquals(1, s.getInputStream().read()); + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertFalse(h.ch.isInputShutdown()); + assertFalse(h.ch.isOutputShutdown()); + + // Make the connection half-closed and ensure read() returns -1. + ch.shutdownOutput().sync(); + assertEquals(-1, s.getInputStream().read()); + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertFalse(h.ch.isInputShutdown()); + assertTrue(h.ch.isOutputShutdown()); + + // If half-closed, the peer should be able to write something. + s.getOutputStream().write(new byte[] { 1 }); + assertEquals(1, (int) h.queue.take()); + } finally { + if (s != null) { + s.close(); + } + if (ch != null) { + ch.close(); + } + ss.close(); + } + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testShutdownOutputAfterClosed(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testShutdownOutputAfterClosed(bootstrap); + } + }); + } + + public void testShutdownOutputAfterClosed(Bootstrap cb) throws Throwable { + TestHandler h = new TestHandler(); + ServerSocket ss = new ServerSocket(); + Socket s = null; + try { + ss.bind(newSocketAddress()); + SocketChannel ch = (SocketChannel) cb.handler(h).connect(ss.getLocalSocketAddress()).sync().channel(); + assertTrue(ch.isActive()); + s = ss.accept(); + + ch.close().syncUninterruptibly(); + try { + ch.shutdownInput().syncUninterruptibly(); + fail(); + } catch (Throwable cause) { + checkThrowable(cause); + } + try { + ch.shutdownOutput().syncUninterruptibly(); + fail(); + } catch (Throwable cause) { + checkThrowable(cause); + } + } finally { + if (s != null) { + s.close(); + } + ss.close(); + } + } + + @Disabled + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testWriteAfterShutdownOutputNoWritabilityChange(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testWriteAfterShutdownOutputNoWritabilityChange(bootstrap); + } + }); + } + + public void testWriteAfterShutdownOutputNoWritabilityChange(Bootstrap cb) throws Throwable { + final TestHandler h = new TestHandler(); + ServerSocket ss = new ServerSocket(); + Socket s = null; + SocketChannel ch = null; + try { + ss.bind(newSocketAddress()); + cb.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(2, 4)); + ch = (SocketChannel) cb.handler(h).connect(ss.getLocalSocketAddress()).sync().channel(); + assumeFalse(ch instanceof OioSocketChannel); + assertTrue(ch.isActive()); + assertFalse(ch.isOutputShutdown()); + + s = ss.accept(); + + byte[] expectedBytes = new byte[]{ 1, 2, 3, 4, 5, 6 }; + ChannelFuture writeFuture = ch.write(Unpooled.wrappedBuffer(expectedBytes)); + h.assertWritability(false); + ch.flush(); + writeFuture.sync(); + h.assertWritability(true); + for (int i = 0; i < expectedBytes.length; ++i) { + assertEquals(expectedBytes[i], s.getInputStream().read()); + } + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertFalse(h.ch.isInputShutdown()); + assertFalse(h.ch.isOutputShutdown()); + + // Make the connection half-closed and ensure read() returns -1. + ch.shutdownOutput().sync(); + assertEquals(-1, s.getInputStream().read()); + + assertTrue(h.ch.isOpen()); + assertTrue(h.ch.isActive()); + assertFalse(h.ch.isInputShutdown()); + assertTrue(h.ch.isOutputShutdown()); + + try { + // If half-closed, the local endpoint shouldn't be able to write + ch.writeAndFlush(Unpooled.wrappedBuffer(new byte[]{ 2 })).sync(); + fail(); + } catch (Throwable cause) { + checkThrowable(cause); + } + assertNull(h.writabilityQueue.poll()); + } finally { + if (s != null) { + s.close(); + } + if (ch != null) { + ch.close(); + } + ss.close(); + } + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testShutdownOutputSoLingerNoAssertError(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testShutdownOutputSoLingerNoAssertError(bootstrap); + } + }); + } + + public void testShutdownOutputSoLingerNoAssertError(Bootstrap cb) throws Throwable { + testShutdownSoLingerNoAssertError0(cb, true); + } + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testShutdownSoLingerNoAssertError(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testShutdownSoLingerNoAssertError(bootstrap); + } + }); + } + + public void testShutdownSoLingerNoAssertError(Bootstrap cb) throws Throwable { + testShutdownSoLingerNoAssertError0(cb, false); + } + + private void testShutdownSoLingerNoAssertError0(Bootstrap cb, boolean output) throws Throwable { + ServerSocket ss = new ServerSocket(); + Socket s = null; + + ChannelFuture cf = null; + try { + ss.bind(newSocketAddress()); + cf = cb.option(ChannelOption.SO_LINGER, 1).handler(new ChannelInboundHandlerAdapter()) + .connect(ss.getLocalSocketAddress()).sync(); + s = ss.accept(); + + cf.sync(); + + if (output) { + ((SocketChannel) cf.channel()).shutdownOutput().sync(); + } else { + ((SocketChannel) cf.channel()).shutdown().sync(); + } + } finally { + if (s != null) { + s.close(); + } + if (cf != null) { + cf.channel().close(); + } + ss.close(); + } + } + private static void checkThrowable(Throwable cause) throws Throwable { + // Depending on OIO / NIO both are ok + if (!(cause instanceof ClosedChannelException) && !(cause instanceof SocketException)) { + throw cause; + } + } + + private static final class TestHandler extends SimpleChannelInboundHandler { + volatile SocketChannel ch; + final BlockingQueue queue = new LinkedBlockingQueue(); + final BlockingDeque writabilityQueue = new LinkedBlockingDeque(); + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + writabilityQueue.add(ctx.channel().isWritable()); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ch = (SocketChannel) ctx.channel(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + queue.offer(msg.readByte()); + } + + private void drainWritabilityQueue() throws InterruptedException { + while ((writabilityQueue.poll(100, TimeUnit.MILLISECONDS)) != null) { + // Just drain the queue. + } + } + + void assertWritability(boolean isWritable) throws InterruptedException { + try { + Boolean writability = writabilityQueue.takeLast(); + assertEquals(isWritable, writability); + // TODO(scott): why do we get multiple writability changes here ... race condition? + drainWritabilityQueue(); + } catch (Throwable c) { + c.printStackTrace(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java new file mode 100644 index 0000000..4c0876d --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java @@ -0,0 +1,323 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.spdy.SpdyFrameCodec; +import io.netty.handler.codec.spdy.SpdyVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SocketSpdyEchoTest extends AbstractSocketTest { + + private static final Random random = new Random(); + static final int ignoredBytes = 20; + + private static ByteBuf createFrames(int version) { + ByteBuf frames = Unpooled.buffer(1174); + + // SPDY UNKNOWN Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(0xFFFF); + frames.writeByte(0xFF); + frames.writeMedium(4); + frames.writeInt(random.nextInt()); + + // SPDY NOOP Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(5); + frames.writeInt(0); + + // SPDY Data Frame + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + frames.writeByte(0x01); + frames.writeMedium(1024); + for (int i = 0; i < 256; i ++) { + frames.writeInt(random.nextInt()); + } + + // SPDY SYN_STREAM Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(1); + frames.writeByte(0x03); + frames.writeMedium(10); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeShort(0x8000); + if (version < 3) { + frames.writeShort(0); + } + + // SPDY SYN_REPLY Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(2); + frames.writeByte(0x01); + frames.writeMedium(4); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + if (version < 3) { + frames.writeInt(0); + } + + // SPDY RST_STREAM Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(3); + frames.writeInt(8); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + frames.writeInt(random.nextInt() | 0x01); + + // SPDY SETTINGS Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(4); + frames.writeByte(0x01); + frames.writeMedium(12); + frames.writeInt(1); + frames.writeByte(0x03); + frames.writeMedium(random.nextInt()); + frames.writeInt(random.nextInt()); + + // SPDY PING Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(6); + frames.writeInt(4); + frames.writeInt(random.nextInt()); + + // SPDY GOAWAY Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(7); + frames.writeInt(8); + frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() | 0x01); + + // SPDY HEADERS Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(8); + frames.writeByte(0x01); + frames.writeMedium(4); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + + // SPDY WINDOW_UPDATE Frame + frames.writeByte(0x80); + frames.writeByte(version); + frames.writeShort(9); + frames.writeInt(8); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + + return frames; + } + + @Test + @Timeout(value = 15000, unit = TimeUnit.MILLISECONDS) + public void testSpdyEcho(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSpdyEcho(serverBootstrap, bootstrap); + } + }); + } + + public void testSpdyEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + logger.info("Testing against SPDY v3.1"); + testSpdyEcho(sb, cb, SpdyVersion.SPDY_3_1, true); + } + + @Test + @Timeout(value = 15000, unit = TimeUnit.MILLISECONDS) + public void testSpdyEchoNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSpdyEchoNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testSpdyEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + logger.info("Testing against SPDY v3.1"); + testSpdyEcho(sb, cb, SpdyVersion.SPDY_3_1, false); + } + + private static void testSpdyEcho( + ServerBootstrap sb, Bootstrap cb, final SpdyVersion version, boolean autoRead) throws Throwable { + + ByteBuf frames; + switch (version) { + case SPDY_3_1: + frames = createFrames(3); + break; + default: + throw new IllegalArgumentException("unknown version"); + } + + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + final SpdyEchoTestServerHandler sh = new SpdyEchoTestServerHandler(autoRead); + final SpdyEchoTestClientHandler ch = new SpdyEchoTestClientHandler(frames.copy(), autoRead); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel channel) throws Exception { + channel.pipeline().addLast( + new SpdyFrameCodec(version), + sh); + } + }); + + cb.handler(ch); + + Channel sc = sb.bind().sync().channel(); + + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + cc.writeAndFlush(frames); + + while (ch.counter < frames.writerIndex() - ignoredBytes) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class SpdyEchoTestServerHandler extends ChannelInboundHandlerAdapter { + private final boolean autoRead; + final AtomicReference exception = new AtomicReference(); + + SpdyEchoTestServerHandler(boolean autoRead) { + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ctx.write(msg); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + try { + ctx.flush(); + } finally { + if (!autoRead) { + ctx.read(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } + + private static class SpdyEchoTestClientHandler extends SimpleChannelInboundHandler { + private final boolean autoRead; + final AtomicReference exception = new AtomicReference(); + final ByteBuf frames; + volatile int counter; + + SpdyEchoTestClientHandler(ByteBuf frames, boolean autoRead) { + this.frames = frames; + this.autoRead = autoRead; + } + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i ++) { + assertEquals(frames.getByte(ignoredBytes + i + lastIdx), actual[i]); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslClientRenegotiateTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslClientRenegotiateTest.java new file mode 100644 index 0000000..ed91927 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslClientRenegotiateTest.java @@ -0,0 +1,267 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.ssl.JdkSslClientContext; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslServerContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.util.concurrent.Future; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.File; +import java.nio.channels.ClosedChannelException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.net.ssl.SSLHandshakeException; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class SocketSslClientRenegotiateTest extends AbstractSocketTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance( + SocketSslClientRenegotiateTest.class); + private static final File CERT_FILE; + private static final File KEY_FILE; + + static { + SelfSignedCertificate ssc; + try { + ssc = new SelfSignedCertificate(); + } catch (CertificateException e) { + throw new Error(e); + } + CERT_FILE = ssc.certificate(); + KEY_FILE = ssc.privateKey(); + } + + private static boolean openSslNotAvailable() { + return !OpenSsl.isAvailable(); + } + + public static Collection data() throws Exception { + List serverContexts = new ArrayList(); + List clientContexts = new ArrayList(); + clientContexts.add(new JdkSslClientContext(CERT_FILE)); + + boolean hasOpenSsl = OpenSsl.isAvailable(); + if (hasOpenSsl) { + OpenSslServerContext context = new OpenSslServerContext(CERT_FILE, KEY_FILE); + serverContexts.add(context); + } else { + logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); + } + + List params = new ArrayList(); + for (SslContext sc: serverContexts) { + for (SslContext cc: clientContexts) { + for (int i = 0; i < 32; i++) { + params.add(new Object[] { sc, cc, true}); + params.add(new Object[] { sc, cc, false}); + } + } + } + + return params; + } + + private final AtomicReference clientException = new AtomicReference(); + private final AtomicReference serverException = new AtomicReference(); + + private volatile Channel clientChannel; + private volatile Channel serverChannel; + + private volatile SslHandler clientSslHandler; + private volatile SslHandler serverSslHandler; + + private final TestHandler clientHandler = new TestHandler(clientException); + + private final TestHandler serverHandler = new TestHandler(serverException); + + @DisabledIf("openSslNotAvailable") + @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}, delegate = {2}") + @MethodSource("data") + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSslRenegotiationRejected(final SslContext serverCtx, final SslContext clientCtx, + final boolean delegate, TestInfo testInfo) throws Throwable { + // BoringSSL does not support renegotiation intentionally. + assumeFalse("BoringSSL".equals(OpenSsl.versionString())); + assumeTrue(OpenSsl.isAvailable()); + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSslRenegotiationRejected(sb, cb, serverCtx, clientCtx, delegate); + } + }); + } + + private static SslHandler newSslHandler(SslContext sslCtx, ByteBufAllocator allocator, Executor executor) { + if (executor == null) { + return sslCtx.newHandler(allocator); + } else { + return sslCtx.newHandler(allocator, executor); + } + } + + public void testSslRenegotiationRejected(ServerBootstrap sb, Bootstrap cb, final SslContext serverCtx, + final SslContext clientCtx, boolean delegate) throws Throwable { + reset(); + + final ExecutorService executorService = delegate ? Executors.newCachedThreadPool() : null; + + try { + sb.childHandler(new ChannelInitializer() { + @Override + @SuppressWarnings("deprecation") + public void initChannel(Channel sch) throws Exception { + serverChannel = sch; + serverSslHandler = newSslHandler(serverCtx, sch.alloc(), executorService); + // As we test renegotiation we should use a protocol that support it. + serverSslHandler.engine().setEnabledProtocols(new String[]{"TLSv1.2"}); + sch.pipeline().addLast("ssl", serverSslHandler); + sch.pipeline().addLast("handler", serverHandler); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + @SuppressWarnings("deprecation") + public void initChannel(Channel sch) throws Exception { + clientChannel = sch; + clientSslHandler = newSslHandler(clientCtx, sch.alloc(), executorService); + // As we test renegotiation we should use a protocol that support it. + clientSslHandler.engine().setEnabledProtocols(new String[]{"TLSv1.2"}); + sch.pipeline().addLast("ssl", clientSslHandler); + sch.pipeline().addLast("handler", clientHandler); + } + }); + + Channel sc = sb.bind().sync().channel(); + cb.connect(sc.localAddress()).sync(); + + Future clientHandshakeFuture = clientSslHandler.handshakeFuture(); + clientHandshakeFuture.sync(); + + String renegotiation = clientSslHandler.engine().getEnabledCipherSuites()[0]; + // Use the first previous enabled ciphersuite and try to renegotiate. + clientSslHandler.engine().setEnabledCipherSuites(new String[]{renegotiation}); + clientSslHandler.renegotiate().await(); + serverChannel.close().awaitUninterruptibly(); + clientChannel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + try { + if (serverException.get() != null) { + throw serverException.get(); + } + fail(); + } catch (DecoderException e) { + assertTrue(e.getCause() instanceof SSLHandshakeException); + } + if (clientException.get() != null) { + throw clientException.get(); + } + } finally { + if (executorService != null) { + executorService.shutdown(); + } + } + } + + private void reset() { + clientException.set(null); + serverException.set(null); + clientHandler.handshakeCounter = 0; + serverHandler.handshakeCounter = 0; + clientChannel = null; + serverChannel = null; + + clientSslHandler = null; + serverSslHandler = null; + } + + @Sharable + private static final class TestHandler extends SimpleChannelInboundHandler { + + protected final AtomicReference exception; + private int handshakeCounter; + + TestHandler(AtomicReference exception) { + this.exception = exception; + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + exception.compareAndSet(null, cause); + ctx.close(); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof SslHandshakeCompletionEvent) { + SslHandshakeCompletionEvent handshakeEvt = (SslHandshakeCompletionEvent) evt; + if (handshakeCounter == 0) { + handshakeCounter++; + if (handshakeEvt.cause() != null) { + logger.warn("Handshake failed:", handshakeEvt.cause()); + } + assertSame(SslHandshakeCompletionEvent.SUCCESS, evt); + } else { + if (ctx.channel().parent() == null) { + assertTrue(handshakeEvt.cause() instanceof ClosedChannelException); + } + } + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java new file mode 100644 index 0000000..a03975f --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java @@ -0,0 +1,596 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.testsuite.util.TestUtils; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLEngine; +import java.io.File; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +public class SocketSslEchoTest extends AbstractSocketTest { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslEchoTest.class); + + private static final int FIRST_MESSAGE_SIZE = 16384; + private static final Random random = new Random(); + private static final File CERT_FILE; + private static final File KEY_FILE; + static final byte[] data = new byte[1048576]; + + static { + random.nextBytes(data); + + SelfSignedCertificate ssc; + try { + ssc = new SelfSignedCertificate(); + } catch (CertificateException e) { + throw new Error(e); + } + CERT_FILE = ssc.certificate(); + KEY_FILE = ssc.privateKey(); + } + + protected enum RenegotiationType { + NONE, // no renegotiation + CLIENT_INITIATED, // renegotiation from client + SERVER_INITIATED, // renegotiation from server + } + + protected static class Renegotiation { + static final Renegotiation NONE = new Renegotiation(RenegotiationType.NONE, null); + + final RenegotiationType type; + final String cipherSuite; + + Renegotiation(RenegotiationType type, String cipherSuite) { + this.type = type; + this.cipherSuite = cipherSuite; + } + + @Override + public String toString() { + if (type == RenegotiationType.NONE) { + return "NONE"; + } + + return type + "(" + cipherSuite + ')'; + } + } + + public static Collection data() throws Exception { + List serverContexts = new ArrayList(); + serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE) + .sslProvider(SslProvider.JDK) + // As we test renegotiation we should use a protocol that support it. + .protocols("TLSv1.2") + .build()); + + List clientContexts = new ArrayList(); + clientContexts.add(SslContextBuilder.forClient() + .sslProvider(SslProvider.JDK) + .trustManager(CERT_FILE) + // As we test renegotiation we should use a protocol that support it. + .protocols("TLSv1.2") + .build()); + + boolean hasOpenSsl = OpenSsl.isAvailable(); + if (hasOpenSsl) { + serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE) + .sslProvider(SslProvider.OPENSSL) + // As we test renegotiation we should use a protocol that support it. + .protocols("TLSv1.2") + .build()); + clientContexts.add(SslContextBuilder.forClient() + .sslProvider(SslProvider.OPENSSL) + .trustManager(CERT_FILE) + // As we test renegotiation we should use a protocol that support it. + .protocols("TLSv1.2") + .build()); + } else { + logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); + } + + List params = new ArrayList(); + for (SslContext sc: serverContexts) { + for (SslContext cc: clientContexts) { + for (RenegotiationType rt: RenegotiationType.values()) { + if (rt != RenegotiationType.NONE && + (sc instanceof OpenSslContext || cc instanceof OpenSslContext)) { + // TODO: OpenSslEngine does not support renegotiation yet. + continue; + } + + final Renegotiation r; + switch (rt) { + case NONE: + r = Renegotiation.NONE; + break; + case SERVER_INITIATED: + r = new Renegotiation(rt, sc.cipherSuites().get(sc.cipherSuites().size() - 1)); + break; + case CLIENT_INITIATED: + r = new Renegotiation(rt, cc.cipherSuites().get(cc.cipherSuites().size() - 1)); + break; + default: + throw new Error(); + } + + for (int i = 0; i < 32; i++) { + params.add(new Object[] { + sc, cc, r, + (i & 16) != 0, (i & 8) != 0, (i & 4) != 0, (i & 2) != 0, (i & 1) != 0 }); + } + } + } + } + + return params; + } + + private final AtomicReference clientException = new AtomicReference(); + private final AtomicReference serverException = new AtomicReference(); + private final AtomicInteger clientSendCounter = new AtomicInteger(); + private final AtomicInteger clientRecvCounter = new AtomicInteger(); + private final AtomicInteger serverRecvCounter = new AtomicInteger(); + + private final AtomicInteger clientNegoCounter = new AtomicInteger(); + private final AtomicInteger serverNegoCounter = new AtomicInteger(); + + private volatile Channel clientChannel; + private volatile Channel serverChannel; + + private volatile SslHandler clientSslHandler; + private volatile SslHandler serverSslHandler; + + private final EchoClientHandler clientHandler = + new EchoClientHandler(clientRecvCounter, clientNegoCounter, clientException); + + private final EchoServerHandler serverHandler = + new EchoServerHandler(serverRecvCounter, serverNegoCounter, serverException); + + private SslContext serverCtx; + private SslContext clientCtx; + private Renegotiation renegotiation; + private boolean serverUsesDelegatedTaskExecutor; + private boolean clientUsesDelegatedTaskExecutor; + private boolean autoRead; + private boolean useChunkedWriteHandler; + private boolean useCompositeByteBuf; + + @AfterAll + public static void compressHeapDumps() throws Exception { + TestUtils.compressHeapDumps(); + } + + @ParameterizedTest(name = + "{index}: serverEngine = {0}, clientEngine = {1}, renegotiation = {2}, " + + "serverUsesDelegatedTaskExecutor = {3}, clientUsesDelegatedTaskExecutor = {4}, " + + "autoRead = {5}, useChunkedWriteHandler = {6}, useCompositeByteBuf = {7}") + @MethodSource("data") + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSslEcho( + SslContext serverCtx, SslContext clientCtx, Renegotiation renegotiation, + boolean serverUsesDelegatedTaskExecutor, boolean clientUsesDelegatedTaskExecutor, + boolean autoRead, boolean useChunkedWriteHandler, boolean useCompositeByteBuf, + TestInfo testInfo) throws Throwable { + this.serverCtx = serverCtx; + this.clientCtx = clientCtx; + this.serverUsesDelegatedTaskExecutor = serverUsesDelegatedTaskExecutor; + this.clientUsesDelegatedTaskExecutor = clientUsesDelegatedTaskExecutor; + this.renegotiation = renegotiation; + this.autoRead = autoRead; + this.useChunkedWriteHandler = useChunkedWriteHandler; + this.useCompositeByteBuf = useCompositeByteBuf; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSslEcho(serverBootstrap, bootstrap); + } + }); + } + + public void testSslEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + final ExecutorService delegatedTaskExecutor = Executors.newCachedThreadPool(); + reset(); + + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) { + serverChannel = sch; + + if (serverUsesDelegatedTaskExecutor) { + SSLEngine sse = serverCtx.newEngine(sch.alloc()); + serverSslHandler = new SslHandler(sse, delegatedTaskExecutor); + } else { + serverSslHandler = serverCtx.newHandler(sch.alloc()); + } + serverSslHandler.setHandshakeTimeoutMillis(0); + + sch.pipeline().addLast("ssl", serverSslHandler); + if (useChunkedWriteHandler) { + sch.pipeline().addLast(new ChunkedWriteHandler()); + } + sch.pipeline().addLast("serverHandler", serverHandler); + } + }); + + final CountDownLatch clientHandshakeEventLatch = new CountDownLatch(1); + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) { + clientChannel = sch; + + if (clientUsesDelegatedTaskExecutor) { + SSLEngine cse = clientCtx.newEngine(sch.alloc()); + clientSslHandler = new SslHandler(cse, delegatedTaskExecutor); + } else { + clientSslHandler = clientCtx.newHandler(sch.alloc()); + } + clientSslHandler.setHandshakeTimeoutMillis(0); + + sch.pipeline().addLast("ssl", clientSslHandler); + if (useChunkedWriteHandler) { + sch.pipeline().addLast(new ChunkedWriteHandler()); + } + sch.pipeline().addLast("clientHandler", clientHandler); + sch.pipeline().addLast(new ChannelInboundHandlerAdapter() { + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof SslHandshakeCompletionEvent) { + clientHandshakeEventLatch.countDown(); + } + ctx.fireUserEventTriggered(evt); + } + }); + } + }); + + final Channel sc = sb.bind().sync().channel(); + cb.connect(sc.localAddress()).sync(); + + final Future clientHandshakeFuture = clientSslHandler.handshakeFuture(); + + // Wait for the handshake to complete before we flush anything. SslHandler should flush non-application data. + clientHandshakeFuture.sync(); + clientHandshakeEventLatch.await(); + + clientChannel.writeAndFlush(Unpooled.wrappedBuffer(data, 0, FIRST_MESSAGE_SIZE)); + clientSendCounter.set(FIRST_MESSAGE_SIZE); + + boolean needsRenegotiation = renegotiation.type == RenegotiationType.CLIENT_INITIATED; + Future renegoFuture = null; + while (clientSendCounter.get() < data.length) { + int clientSendCounterVal = clientSendCounter.get(); + int length = Math.min(random.nextInt(1024 * 64), data.length - clientSendCounterVal); + ByteBuf buf = Unpooled.wrappedBuffer(data, clientSendCounterVal, length); + if (useCompositeByteBuf) { + buf = Unpooled.compositeBuffer().addComponent(true, buf); + } + + ChannelFuture future = clientChannel.writeAndFlush(buf); + clientSendCounter.set(clientSendCounterVal += length); + future.sync(); + + if (needsRenegotiation && clientSendCounterVal >= data.length / 2) { + needsRenegotiation = false; + clientSslHandler.engine().setEnabledCipherSuites(new String[] { renegotiation.cipherSuite }); + renegoFuture = clientSslHandler.renegotiate(); + logStats("CLIENT RENEGOTIATES"); + assertThat(renegoFuture, is(not(sameInstance(clientHandshakeFuture)))); + } + } + + // Ensure all data has been exchanged. + while (clientRecvCounter.get() < data.length) { + if (serverException.get() != null) { + break; + } + if (clientException.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (serverRecvCounter.get() < data.length) { + if (serverException.get() != null) { + break; + } + if (clientException.get() != null) { + break; + } + + Thread.sleep(50); + } + + // Wait until renegotiation is done. + if (renegoFuture != null) { + renegoFuture.sync(); + } + if (serverHandler.renegoFuture != null) { + serverHandler.renegoFuture.sync(); + } + + serverChannel.close().awaitUninterruptibly(); + clientChannel.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + delegatedTaskExecutor.shutdown(); + + if (serverException.get() != null && !(serverException.get() instanceof IOException)) { + throw serverException.get(); + } + if (clientException.get() != null && !(clientException.get() instanceof IOException)) { + throw clientException.get(); + } + if (serverException.get() != null) { + throw serverException.get(); + } + if (clientException.get() != null) { + throw clientException.get(); + } + + // When renegotiation is done, at least the initiating side should be notified. + try { + switch (renegotiation.type) { + case SERVER_INITIATED: + assertThat(serverSslHandler.engine().getSession().getCipherSuite(), is(renegotiation.cipherSuite)); + assertThat(serverNegoCounter.get(), is(2)); + assertThat(clientNegoCounter.get(), anyOf(is(1), is(2))); + break; + case CLIENT_INITIATED: + assertThat(serverNegoCounter.get(), anyOf(is(1), is(2))); + assertThat(clientSslHandler.engine().getSession().getCipherSuite(), is(renegotiation.cipherSuite)); + assertThat(clientNegoCounter.get(), is(2)); + break; + case NONE: + assertThat(serverNegoCounter.get(), is(1)); + assertThat(clientNegoCounter.get(), is(1)); + } + } finally { + logStats("STATS"); + } + } + + private void reset() { + clientException.set(null); + serverException.set(null); + + clientSendCounter.set(0); + clientRecvCounter.set(0); + serverRecvCounter.set(0); + + clientNegoCounter.set(0); + serverNegoCounter.set(0); + + clientChannel = null; + serverChannel = null; + + clientSslHandler = null; + serverSslHandler = null; + } + + void logStats(String message) { + logger.debug( + "{}:\n" + + "\tclient { sent: {}, rcvd: {}, nego: {}, cipher: {} },\n" + + "\tserver { rcvd: {}, nego: {}, cipher: {} }", + message, + clientSendCounter, clientRecvCounter, clientNegoCounter, + clientSslHandler.engine().getSession().getCipherSuite(), + serverRecvCounter, serverNegoCounter, + serverSslHandler.engine().getSession().getCipherSuite()); + } + + @Sharable + private abstract class EchoHandler extends SimpleChannelInboundHandler { + + protected final AtomicInteger recvCounter; + protected final AtomicInteger negoCounter; + protected final AtomicReference exception; + + EchoHandler( + AtomicInteger recvCounter, AtomicInteger negoCounter, + AtomicReference exception) { + + this.recvCounter = recvCounter; + this.negoCounter = negoCounter; + this.exception = exception; + } + + @Override + public final void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + // We intentionally do not ctx.flush() here because we want to verify the SslHandler correctly flushing + // non-application and previously flushed writes internally. + if (!autoRead) { + ctx.read(); + } + ctx.fireChannelReadComplete(); + } + + @Override + public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof SslHandshakeCompletionEvent) { + SslHandshakeCompletionEvent handshakeEvt = (SslHandshakeCompletionEvent) evt; + if (handshakeEvt.cause() != null) { + logger.warn("Handshake failed:", handshakeEvt.cause()); + } + assertSame(SslHandshakeCompletionEvent.SUCCESS, evt); + negoCounter.incrementAndGet(); + logStats("HANDSHAKEN"); + } + ctx.fireUserEventTriggered(evt); + } + + @Override + public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception from the client side:", cause); + } + + exception.compareAndSet(null, cause); + ctx.close(); + } + } + + private class EchoClientHandler extends EchoHandler { + + EchoClientHandler( + AtomicInteger recvCounter, AtomicInteger negoCounter, + AtomicReference exception) { + + super(recvCounter, negoCounter, exception); + } + + @Override + public void handlerAdded(final ChannelHandlerContext ctx) { + if (!autoRead) { + ctx.pipeline().get(SslHandler.class).handshakeFuture().addListener( + new GenericFutureListener>() { + @Override + public void operationComplete(Future future) { + ctx.read(); + } + }); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = recvCounter.get(); + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + recvCounter.addAndGet(actual.length); + } + } + + private class EchoServerHandler extends EchoHandler { + volatile Future renegoFuture; + + EchoServerHandler( + AtomicInteger recvCounter, AtomicInteger negoCounter, + AtomicReference exception) { + + super(recvCounter, negoCounter, exception); + } + + @Override + public final void channelRegistered(ChannelHandlerContext ctx) { + renegoFuture = null; + } + + @Override + public void channelActive(final ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + ctx.fireChannelActive(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = recvCounter.get(); + for (int i = 0; i < actual.length; i ++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + ByteBuf buf = Unpooled.wrappedBuffer(actual); + if (useCompositeByteBuf) { + buf = Unpooled.compositeBuffer().addComponent(true, buf); + } + ctx.writeAndFlush(buf); + + recvCounter.addAndGet(actual.length); + + // Perform server-initiated renegotiation if necessary. + if (renegotiation.type == RenegotiationType.SERVER_INITIATED && + recvCounter.get() > data.length / 2 && renegoFuture == null) { + + SslHandler sslHandler = ctx.pipeline().get(SslHandler.class); + + Future hf = sslHandler.handshakeFuture(); + assertThat(hf.isDone(), is(true)); + + sslHandler.engine().setEnabledCipherSuites(new String[] { renegotiation.cipherSuite }); + logStats("SERVER RENEGOTIATES"); + renegoFuture = sslHandler.renegotiate(); + assertThat(renegoFuture, is(not(sameInstance(hf)))); + assertThat(renegoFuture, is(sameInstance(sslHandler.handshakeFuture()))); + assertThat(renegoFuture.isDone(), is(false)); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java new file mode 100644 index 0000000..7cd3ece --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslGreetingTest.java @@ -0,0 +1,273 @@ +/* +* Copyright 2014 The Netty Project +* +* The Netty Project licenses this file to you 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: +* +* https://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. +*/ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslHandshakeCompletionEvent; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import java.io.File; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +public class SocketSslGreetingTest extends AbstractSocketTest { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslGreetingTest.class); + + private static final LogLevel LOG_LEVEL = LogLevel.TRACE; + private static final File CERT_FILE; + private static final File KEY_FILE; + + static { + SelfSignedCertificate ssc; + try { + ssc = new SelfSignedCertificate(); + } catch (CertificateException e) { + throw new Error(e); + } + CERT_FILE = ssc.certificate(); + KEY_FILE = ssc.privateKey(); + } + + public static Collection data() throws Exception { + List serverContexts = new ArrayList(); + serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE).sslProvider(SslProvider.JDK).build()); + + List clientContexts = new ArrayList(); + clientContexts.add(SslContextBuilder.forClient().sslProvider(SslProvider.JDK).trustManager(CERT_FILE).build()); + + boolean hasOpenSsl = OpenSsl.isAvailable(); + if (hasOpenSsl) { + serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE) + .sslProvider(SslProvider.OPENSSL).build()); + clientContexts.add(SslContextBuilder.forClient().sslProvider(SslProvider.OPENSSL) + .trustManager(CERT_FILE).build()); + } else { + logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); + } + + List params = new ArrayList(); + for (SslContext sc: serverContexts) { + for (SslContext cc: clientContexts) { + params.add(new Object[] { sc, cc, true }); + params.add(new Object[] { sc, cc, false }); + } + } + return params; + } + + private static SslHandler newSslHandler(SslContext sslCtx, ByteBufAllocator allocator, Executor executor) { + if (executor == null) { + return sslCtx.newHandler(allocator); + } else { + return sslCtx.newHandler(allocator, executor); + } + } + + // Test for https://github.com/netty/netty/pull/2437 + @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}, delegate = {2}") + @MethodSource("data") + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSslGreeting(final SslContext serverCtx, final SslContext clientCtx, final boolean delegate, + TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSslGreeting(sb, cb, serverCtx, clientCtx, delegate); + } + }); + } + + public void testSslGreeting(ServerBootstrap sb, Bootstrap cb, final SslContext serverCtx, + final SslContext clientCtx, boolean delegate) throws Throwable { + final ServerHandler sh = new ServerHandler(); + final ClientHandler ch = new ClientHandler(); + + final ExecutorService executorService = delegate ? Executors.newCachedThreadPool() : null; + try { + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + ChannelPipeline p = sch.pipeline(); + p.addLast(newSslHandler(serverCtx, sch.alloc(), executorService)); + p.addLast(new LoggingHandler(LOG_LEVEL)); + p.addLast(sh); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + ChannelPipeline p = sch.pipeline(); + p.addLast(newSslHandler(clientCtx, sch.alloc(), executorService)); + p.addLast(new LoggingHandler(LOG_LEVEL)); + p.addLast(ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + ch.latch.await(); + + sh.channel.close().awaitUninterruptibly(); + cc.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } finally { + if (executorService != null) { + executorService.shutdown(); + } + } + } + + private static class ClientHandler extends SimpleChannelInboundHandler { + + final AtomicReference exception = new AtomicReference(); + final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + assertEquals('a', buf.readByte()); + assertFalse(buf.isReadable()); + latch.countDown(); + ctx.close(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception from the client side", cause); + } + + exception.compareAndSet(null, cause); + ctx.close(); + } + } + + private static class ServerHandler extends SimpleChannelInboundHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + + @Override + protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { + // discard + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + channel.writeAndFlush(ctx.alloc().buffer().writeByte('a')); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception from the server side", cause); + } + + exception.compareAndSet(null, cause); + ctx.close(); + } + + @Override + public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) throws Exception { + if (evt instanceof SslHandshakeCompletionEvent) { + final SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt; + if (event.isSuccess()) { + SSLSession session = ctx.pipeline().get(SslHandler.class).engine().getSession(); + try { + session.getPeerCertificates(); + fail(); + } catch (SSLPeerUnverifiedException e) { + // expected + } + try { + session.getPeerCertificateChain(); + fail(); + } catch (SSLPeerUnverifiedException e) { + // expected + } catch (UnsupportedOperationException e) { + // Starting from Java15 this method throws UnsupportedOperationException as it was + // deprecated before and getPeerCertificates() should be used + if (PlatformDependent.javaVersion() < 15) { + throw e; + } + } + try { + session.getPeerPrincipal(); + fail(); + } catch (SSLPeerUnverifiedException e) { + // expected + } + } + } + ctx.fireUserEventTriggered(evt); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslSessionReuseTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslSessionReuseTest.java new file mode 100644 index 0000000..f55c0ce --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketSslSessionReuseTest.java @@ -0,0 +1,216 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.ssl.JdkSslClientContext; +import io.netty.handler.ssl.JdkSslServerContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSessionContext; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.cert.CertificateException; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SocketSslSessionReuseTest extends AbstractSocketTest { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketSslSessionReuseTest.class); + + private static final File CERT_FILE; + private static final File KEY_FILE; + + static { + SelfSignedCertificate ssc; + try { + ssc = new SelfSignedCertificate(); + } catch (CertificateException e) { + throw new Error(e); + } + CERT_FILE = ssc.certificate(); + KEY_FILE = ssc.privateKey(); + } + + public static Collection data() throws Exception { + return Collections.singletonList(new Object[] { + new JdkSslServerContext(CERT_FILE, KEY_FILE), + new JdkSslClientContext(CERT_FILE) + }); + } + + @ParameterizedTest(name = "{index}: serverEngine = {0}, clientEngine = {1}") + @MethodSource("data") + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testSslSessionReuse(final SslContext serverCtx, final SslContext clientCtx, TestInfo testInfo) + throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testSslSessionReuse(sb, cb, serverCtx, clientCtx); + } + }); + } + + public void testSslSessionReuse(ServerBootstrap sb, Bootstrap cb, + final SslContext serverCtx, final SslContext clientCtx) throws Throwable { + final ReadAndDiscardHandler sh = new ReadAndDiscardHandler(true, true); + final ReadAndDiscardHandler ch = new ReadAndDiscardHandler(false, true); + final String[] protocols = { "TLSv1", "TLSv1.1", "TLSv1.2" }; + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel sch) throws Exception { + SSLEngine engine = serverCtx.newEngine(sch.alloc()); + engine.setUseClientMode(false); + engine.setEnabledProtocols(protocols); + + sch.pipeline().addLast(new SslHandler(engine)); + sch.pipeline().addLast(sh); + } + }); + final Channel sc = sb.bind().sync().channel(); + + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel sch) throws Exception { + InetSocketAddress serverAddr = (InetSocketAddress) sc.localAddress(); + SSLEngine engine = clientCtx.newEngine(sch.alloc(), serverAddr.getHostString(), serverAddr.getPort()); + engine.setUseClientMode(true); + engine.setEnabledProtocols(protocols); + + sch.pipeline().addLast(new SslHandler(engine)); + sch.pipeline().addLast(ch); + } + }); + + try { + SSLSessionContext clientSessionCtx = clientCtx.sessionContext(); + ByteBuf msg = Unpooled.wrappedBuffer(new byte[] { 0xa, 0xb, 0xc, 0xd }, 0, 4); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + cc.writeAndFlush(msg).sync(); + cc.closeFuture().sync(); + rethrowHandlerExceptions(sh, ch); + Set sessions = sessionIdSet(clientSessionCtx.getIds()); + + msg = Unpooled.wrappedBuffer(new byte[] { 0xa, 0xb, 0xc, 0xd }, 0, 4); + cc = cb.connect(sc.localAddress()).sync().channel(); + cc.writeAndFlush(msg).sync(); + cc.closeFuture().sync(); + assertEquals(sessions, sessionIdSet(clientSessionCtx.getIds()), "Expected no new sessions"); + rethrowHandlerExceptions(sh, ch); + } finally { + sc.close().awaitUninterruptibly(); + } + } + + private static void rethrowHandlerExceptions(ReadAndDiscardHandler sh, ReadAndDiscardHandler ch) throws Throwable { + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static Set sessionIdSet(Enumeration sessionIds) { + Set idSet = new HashSet(); + byte[] id; + while (sessionIds.hasMoreElements()) { + id = sessionIds.nextElement(); + idSet.add(ByteBufUtil.hexDump(Unpooled.wrappedBuffer(id))); + } + return idSet; + } + + @Sharable + private static class ReadAndDiscardHandler extends SimpleChannelInboundHandler { + final AtomicReference exception = new AtomicReference(); + private final boolean server; + private final boolean autoRead; + + ReadAndDiscardHandler(boolean server, boolean autoRead) { + this.server = server; + this.autoRead = autoRead; + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + ctx.close(); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + try { + ctx.flush(); + } finally { + if (!autoRead) { + ctx.read(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn( + "Unexpected exception from the " + + (server? "server" : "client") + " side", cause); + } + + exception.compareAndSet(null, cause); + ctx.close(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java new file mode 100644 index 0000000..d5700e6 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStartTlsTest.java @@ -0,0 +1,338 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import io.netty.util.concurrent.Future; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLEngine; +import java.io.File; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SocketStartTlsTest extends AbstractSocketTest { + private static final String PARAMETERIZED_NAME = "{index}: serverEngine = {0}, clientEngine = {1}"; + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocketStartTlsTest.class); + + private static final LogLevel LOG_LEVEL = LogLevel.TRACE; + private static final File CERT_FILE; + private static final File KEY_FILE; + private static EventExecutorGroup executor; + + static { + SelfSignedCertificate ssc; + try { + ssc = new SelfSignedCertificate(); + } catch (CertificateException e) { + throw new Error(e); + } + CERT_FILE = ssc.certificate(); + KEY_FILE = ssc.privateKey(); + } + + public static Collection data() throws Exception { + List serverContexts = new ArrayList(); + serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE).sslProvider(SslProvider.JDK).build()); + + List clientContexts = new ArrayList(); + clientContexts.add(SslContextBuilder.forClient().sslProvider(SslProvider.JDK).trustManager(CERT_FILE).build()); + + boolean hasOpenSsl = OpenSsl.isAvailable(); + if (hasOpenSsl) { + serverContexts.add(SslContextBuilder.forServer(CERT_FILE, KEY_FILE) + .sslProvider(SslProvider.OPENSSL).build()); + clientContexts.add(SslContextBuilder.forClient().sslProvider(SslProvider.OPENSSL) + .trustManager(CERT_FILE).build()); + } else { + logger.warn("OpenSSL is unavailable and thus will not be tested.", OpenSsl.unavailabilityCause()); + } + + List params = new ArrayList(); + for (SslContext sc: serverContexts) { + for (SslContext cc: clientContexts) { + params.add(new Object[] { sc, cc }); + } + } + return params; + } + + @BeforeAll + public static void createExecutor() { + executor = new DefaultEventExecutorGroup(2); + } + + @AfterAll + public static void shutdownExecutor() throws Exception { + executor.shutdownGracefully().sync(); + } + + @ParameterizedTest(name = PARAMETERIZED_NAME) + @MethodSource("data") + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testStartTls(final SslContext serverCtx, final SslContext clientCtx, TestInfo testInfo) + throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testStartTls(sb, cb, serverCtx, clientCtx); + } + }); + } + + public void testStartTls(ServerBootstrap sb, Bootstrap cb, + SslContext serverCtx, SslContext clientCtx) throws Throwable { + testStartTls(sb, cb, serverCtx, clientCtx, true); + } + + @ParameterizedTest(name = PARAMETERIZED_NAME) + @MethodSource("data") + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testStartTlsNotAutoRead(final SslContext serverCtx, final SslContext clientCtx, + TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testStartTlsNotAutoRead(sb, cb, serverCtx, clientCtx); + } + }); + } + + public void testStartTlsNotAutoRead(ServerBootstrap sb, Bootstrap cb, + SslContext serverCtx, SslContext clientCtx) throws Throwable { + testStartTls(sb, cb, serverCtx, clientCtx, false); + } + + private void testStartTls(ServerBootstrap sb, Bootstrap cb, + SslContext serverCtx, SslContext clientCtx, boolean autoRead) throws Throwable { + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + final EventExecutorGroup executor = SocketStartTlsTest.executor; + SSLEngine sse = serverCtx.newEngine(PooledByteBufAllocator.DEFAULT); + SSLEngine cse = clientCtx.newEngine(PooledByteBufAllocator.DEFAULT); + + final StartTlsServerHandler sh = new StartTlsServerHandler(sse, autoRead); + final StartTlsClientHandler ch = new StartTlsClientHandler(cse, autoRead); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + ChannelPipeline p = sch.pipeline(); + p.addLast("logger", new LoggingHandler(LOG_LEVEL)); + p.addLast(new LineBasedFrameDecoder(64), new StringDecoder(), new StringEncoder()); + p.addLast(executor, sh); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + ChannelPipeline p = sch.pipeline(); + p.addLast("logger", new LoggingHandler(LOG_LEVEL)); + p.addLast(new LineBasedFrameDecoder(64), new StringDecoder(), new StringEncoder()); + p.addLast(executor, ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + while (cc.isActive()) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + while (sh.channel.isActive()) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + Thread.sleep(50); + } + + sh.channel.close().awaitUninterruptibly(); + cc.close().awaitUninterruptibly(); + sc.close().awaitUninterruptibly(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class StartTlsClientHandler extends SimpleChannelInboundHandler { + private final SslHandler sslHandler; + private final boolean autoRead; + private Future handshakeFuture; + final AtomicReference exception = new AtomicReference(); + + StartTlsClientHandler(SSLEngine engine, boolean autoRead) { + engine.setUseClientMode(true); + sslHandler = new SslHandler(engine); + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + if (!autoRead) { + ctx.read(); + } + ctx.writeAndFlush("StartTlsRequest\n"); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { + if ("StartTlsResponse".equals(msg)) { + ctx.pipeline().addAfter("logger", "ssl", sslHandler); + handshakeFuture = sslHandler.handshakeFuture(); + ctx.writeAndFlush("EncryptedRequest\n"); + return; + } + + assertEquals("EncryptedResponse", msg); + assertNotNull(handshakeFuture); + assertTrue(handshakeFuture.isSuccess()); + ctx.close(); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception from the client side", cause); + } + + exception.compareAndSet(null, cause); + ctx.close(); + } + } + + private static class StartTlsServerHandler extends SimpleChannelInboundHandler { + private final SslHandler sslHandler; + private final boolean autoRead; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + + StartTlsServerHandler(SSLEngine engine, boolean autoRead) { + engine.setUseClientMode(false); + sslHandler = new SslHandler(engine, true); + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { + if ("StartTlsRequest".equals(msg)) { + ctx.pipeline().addAfter("logger", "ssl", sslHandler); + ctx.writeAndFlush("StartTlsResponse\n"); + return; + } + + assertEquals("EncryptedRequest", msg); + ctx.writeAndFlush("EncryptedResponse\n"); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Unexpected exception from the server side", cause); + } + + exception.compareAndSet(null, cause); + ctx.close(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStringEchoTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStringEchoTest.java new file mode 100644 index 0000000..293f649 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketStringEchoTest.java @@ -0,0 +1,206 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import io.netty.handler.codec.Delimiters; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.util.CharsetUtil; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +public class SocketStringEchoTest extends AbstractSocketTest { + + static final Random random = new Random(); + static final String[] data = new String[1024]; + + static { + for (int i = 0; i < data.length; i ++) { + int eLen = random.nextInt(512); + char[] e = new char[eLen]; + for (int j = 0; j < eLen; j ++) { + e[j] = (char) ('a' + random.nextInt(26)); + } + + data[i] = new String(e); + } + } + + @Test + @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + public void testStringEcho(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testStringEcho(serverBootstrap, bootstrap); + } + }); + } + + public void testStringEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testStringEcho(sb, cb, true); + } + + @Test + @Timeout(value = 60000, unit = TimeUnit.MILLISECONDS) + public void testStringEchoNotAutoRead(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testStringEchoNotAutoRead(serverBootstrap, bootstrap); + } + }); + } + + public void testStringEchoNotAutoRead(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testStringEcho(sb, cb, false); + } + + private static void testStringEcho(ServerBootstrap sb, Bootstrap cb, boolean autoRead) throws Throwable { + sb.childOption(ChannelOption.AUTO_READ, autoRead); + cb.option(ChannelOption.AUTO_READ, autoRead); + + Promise serverDonePromise = ImmediateEventExecutor.INSTANCE.newPromise(); + Promise clientDonePromise = ImmediateEventExecutor.INSTANCE.newPromise(); + final StringEchoHandler sh = new StringEchoHandler(autoRead, serverDonePromise); + final StringEchoHandler ch = new StringEchoHandler(autoRead, clientDonePromise); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + sch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(512, Delimiters.lineDelimiter())); + sch.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.ISO_8859_1)); + sch.pipeline().addBefore("decoder", "encoder", new StringEncoder(CharsetUtil.ISO_8859_1)); + sch.pipeline().addAfter("decoder", "handler", sh); + } + }); + + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(Channel sch) throws Exception { + sch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(512, Delimiters.lineDelimiter())); + sch.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.ISO_8859_1)); + sch.pipeline().addBefore("decoder", "encoder", new StringEncoder(CharsetUtil.ISO_8859_1)); + sch.pipeline().addAfter("decoder", "handler", ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + for (String element : data) { + String delimiter = random.nextBoolean() ? "\r\n" : "\n"; + cc.writeAndFlush(element + delimiter); + } + + ch.donePromise.sync(); + sh.donePromise.sync(); + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + static class StringEchoHandler extends SimpleChannelInboundHandler { + private final boolean autoRead; + private final Promise donePromise; + private int dataIndex; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + + StringEchoHandler(boolean autoRead, Promise donePromise) { + this.autoRead = autoRead; + this.donePromise = donePromise; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + if (!autoRead) { + ctx.read(); + } + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { + if (!data[dataIndex].equals(msg)) { + donePromise.tryFailure(new IllegalStateException("index: " + dataIndex + " didn't match!")); + ctx.close(); + return; + } + + if (channel.parent() != null) { + String delimiter = random.nextBoolean() ? "\r\n" : "\n"; + channel.write(msg + delimiter); + } + + if (++dataIndex >= data.length) { + donePromise.setSuccess(null); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + try { + ctx.flush(); + } finally { + if (!autoRead) { + ctx.read(); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + donePromise.tryFailure(new IllegalStateException("exceptionCaught: " + ctx.channel(), cause)); + ctx.close(); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + donePromise.tryFailure(new IllegalStateException("channelInactive: " + ctx.channel())); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java new file mode 100644 index 0000000..cace51b --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java @@ -0,0 +1,223 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.oio.OioEventLoopGroup; +import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.channel.socket.oio.OioDatagramChannel; +import io.netty.channel.socket.oio.OioServerSocketChannel; +import io.netty.channel.socket.oio.OioSocketChannel; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapComboFactory; +import io.netty.testsuite.transport.TestsuitePermutation.BootstrapFactory; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.internal.SystemPropertyUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SocketTestPermutation { + + static final String BAD_HOST = SystemPropertyUtil.get("io.netty.testsuite.badHost", "198.51.100.254"); + static final int BAD_PORT = SystemPropertyUtil.getInt("io.netty.testsuite.badPort", 65535); + + static { + InternalLogger logger = InternalLoggerFactory.getInstance(SocketConnectionAttemptTest.class); + logger.debug("-Dio.netty.testsuite.badHost: {}", BAD_HOST); + logger.debug("-Dio.netty.testsuite.badPort: {}", BAD_PORT); + } + + static final SocketTestPermutation INSTANCE = new SocketTestPermutation(); + + protected static final int BOSSES = 2; + protected static final int WORKERS = 3; + + protected static final int OIO_SO_TIMEOUT = 10; // Use short timeout for faster runs. + + protected final EventLoopGroup nioBossGroup = + new NioEventLoopGroup(BOSSES, new DefaultThreadFactory("testsuite-nio-boss", true)); + protected final EventLoopGroup nioWorkerGroup = + new NioEventLoopGroup(WORKERS, new DefaultThreadFactory("testsuite-nio-worker", true)); + protected final EventLoopGroup oioBossGroup = + new OioEventLoopGroup(Integer.MAX_VALUE, new DefaultThreadFactory("testsuite-oio-boss", true)); + protected final EventLoopGroup oioWorkerGroup = + new OioEventLoopGroup(Integer.MAX_VALUE, new DefaultThreadFactory("testsuite-oio-worker", true)); + + protected , B extends AbstractBootstrap> + + List> combo(List> sbfs, List> cbfs) { + + List> list = new ArrayList>(); + + // Populate the combinations + for (BootstrapFactory sbf: sbfs) { + for (BootstrapFactory cbf: cbfs) { + final BootstrapFactory sbf0 = sbf; + final BootstrapFactory cbf0 = cbf; + list.add(new BootstrapComboFactory() { + @Override + public A newServerInstance() { + return sbf0.newInstance(); + } + + @Override + public B newClientInstance() { + return cbf0.newInstance(); + } + }); + } + } + + return list; + } + + public List> socket() { + // Make the list of ServerBootstrap factories. + List> sbfs = serverSocket(); + + // Make the list of Bootstrap factories. + List> cbfs = clientSocket(); + + // Populate the combinations + List> list = combo(sbfs, cbfs); + + // Remove the OIO-OIO case which often leads to a dead lock by its nature. + list.remove(list.size() - 1); + + return list; + } + + public List> socketWithFastOpen() { + // Make the list of ServerBootstrap factories. + List> sbfs = serverSocket(); + + // Make the list of Bootstrap factories. + List> cbfs = clientSocketWithFastOpen(); + + // Populate the combinations + List> list = combo(sbfs, cbfs); + + // Remove the OIO-OIO case which often leads to a dead lock by its nature. + list.remove(list.size() - 1); + + return list; + } + + public List> datagram(final InternetProtocolFamily family) { + // Make the list of Bootstrap factories. + List> bfs = Arrays.asList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channelFactory(new ChannelFactory() { + @Override + public Channel newChannel() { + return new NioDatagramChannel(family); + } + + @Override + public String toString() { + return NioDatagramChannel.class.getSimpleName() + ".class"; + } + }); + } + }, + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(oioWorkerGroup).channel(OioDatagramChannel.class) + .option(ChannelOption.SO_TIMEOUT, OIO_SO_TIMEOUT); + } + } + ); + + // Populare the combinations. + return combo(bfs, bfs); + } + + public List> serverSocket() { + return Arrays.asList( + new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(nioBossGroup, nioWorkerGroup) + .channel(NioServerSocketChannel.class); + } + }, + new BootstrapFactory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap().group(oioBossGroup, oioWorkerGroup) + .channel(OioServerSocketChannel.class) + .option(ChannelOption.SO_TIMEOUT, OIO_SO_TIMEOUT); + } + } + ); + } + + public List> clientSocket() { + return Arrays.asList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channel(NioSocketChannel.class); + } + }, + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(oioWorkerGroup).channel(OioSocketChannel.class) + .option(ChannelOption.SO_TIMEOUT, OIO_SO_TIMEOUT); + } + } + ); + } + + public List> clientSocketWithFastOpen() { + return clientSocket(); + } + + public List> datagramSocket() { + return Arrays.asList( + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(nioWorkerGroup).channel(NioDatagramChannel.class); + } + }, + new BootstrapFactory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(oioWorkerGroup).channel(OioDatagramChannel.class) + .option(ChannelOption.SO_TIMEOUT, OIO_SO_TIMEOUT); + } + } + ); + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/TrafficShapingHandlerTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/TrafficShapingHandlerTest.java new file mode 100644 index 0000000..b4ce39e --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/TrafficShapingHandlerTest.java @@ -0,0 +1,604 @@ +/* + * Copyright 2012 The Netty Project + * The Netty Project licenses this file to you 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: + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.traffic.AbstractTrafficShapingHandler; +import io.netty.handler.traffic.ChannelTrafficShapingHandler; +import io.netty.handler.traffic.GlobalTrafficShapingHandler; +import io.netty.handler.traffic.TrafficCounter; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TrafficShapingHandlerTest extends AbstractSocketTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(TrafficShapingHandlerTest.class); + private static final InternalLogger loggerServer = InternalLoggerFactory.getInstance("ServerTSH"); + private static final InternalLogger loggerClient = InternalLoggerFactory.getInstance("ClientTSH"); + + static final int messageSize = 1024; + static final int bandwidthFactor = 12; + static final int minfactor = 3; + static final int maxfactor = bandwidthFactor + bandwidthFactor / 2; + static final long stepms = (1000 / bandwidthFactor - 10) / 10 * 10; + static final long minimalms = Math.max(stepms / 2, 20) / 10 * 10; + static final long check = 10; + private static final Random random = new Random(); + static final byte[] data = new byte[messageSize]; + + private static final String TRAFFIC = "traffic"; + private static String currentTestName; + private static int currentTestRun; + + private static EventExecutorGroup group; + private static EventExecutorGroup groupForGlobal; + private static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(10); + static { + random.nextBytes(data); + } + + @BeforeAll + public static void createGroup() { + logger.info("Bandwidth: " + minfactor + " <= " + bandwidthFactor + " <= " + maxfactor + + " StepMs: " + stepms + " MinMs: " + minimalms + " CheckMs: " + check); + group = new DefaultEventExecutorGroup(8); + groupForGlobal = new DefaultEventExecutorGroup(8); + } + + @AfterAll + public static void destroyGroup() throws Exception { + group.shutdownGracefully().sync(); + groupForGlobal.shutdownGracefully().sync(); + executor.shutdown(); + } + + private static long[] computeWaitRead(int[] multipleMessage) { + long[] minimalWaitBetween = new long[multipleMessage.length + 1]; + minimalWaitBetween[0] = 0; + for (int i = 0; i < multipleMessage.length; i++) { + if (multipleMessage[i] > 1) { + minimalWaitBetween[i + 1] = (multipleMessage[i] - 1) * stepms + minimalms; + } else { + minimalWaitBetween[i + 1] = 10; + } + } + return minimalWaitBetween; + } + + private static long[] computeWaitWrite(int[] multipleMessage) { + long[] minimalWaitBetween = new long[multipleMessage.length + 1]; + for (int i = 0; i < multipleMessage.length; i++) { + if (multipleMessage[i] > 1) { + minimalWaitBetween[i] = (multipleMessage[i] - 1) * stepms + minimalms; + } else { + minimalWaitBetween[i] = 10; + } + } + return minimalWaitBetween; + } + + private static long[] computeWaitAutoRead(int []autoRead) { + long [] minimalWaitBetween = new long[autoRead.length + 1]; + minimalWaitBetween[0] = 0; + for (int i = 0; i < autoRead.length; i++) { + if (autoRead[i] != 0) { + if (autoRead[i] > 0) { + minimalWaitBetween[i + 1] = -1; + } else { + minimalWaitBetween[i + 1] = check; + } + } else { + minimalWaitBetween[i + 1] = 0; + } + } + return minimalWaitBetween; + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testNoTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST NO TRAFFIC"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testNoTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testNoTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 2, 1 }; + long[] minimalWaitBetween = null; + testTrafficShapping0(sb, cb, false, false, false, false, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testWriteTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST WRITE"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testWriteTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testWriteTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 2, 1, 1 }; + long[] minimalWaitBetween = computeWaitWrite(multipleMessage); + testTrafficShapping0(sb, cb, false, false, true, false, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testReadTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST READ"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testReadTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testReadTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 2, 1, 1 }; + long[] minimalWaitBetween = computeWaitRead(multipleMessage); + testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testWrite1TrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST WRITE"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testWrite1TrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testWrite1TrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 1, 1 }; + long[] minimalWaitBetween = computeWaitWrite(multipleMessage); + testTrafficShapping0(sb, cb, false, false, true, false, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testRead1TrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST READ"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testRead1TrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testRead1TrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 1, 1 }; + long[] minimalWaitBetween = computeWaitRead(multipleMessage); + testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testWriteGlobalTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST GLOBAL WRITE"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testWriteGlobalTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testWriteGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 2, 1, 1 }; + long[] minimalWaitBetween = computeWaitWrite(multipleMessage); + testTrafficShapping0(sb, cb, false, false, true, true, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testReadGlobalTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST GLOBAL READ"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testReadGlobalTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testReadGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = null; + int[] multipleMessage = { 1, 2, 1, 1 }; + long[] minimalWaitBetween = computeWaitRead(multipleMessage); + testTrafficShapping0(sb, cb, false, true, false, true, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testAutoReadTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST AUTO READ"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAutoReadTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testAutoReadTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = { 1, -1, -1, 1, -2, 0, 1, 0, -3, 0, 1, 2, 0 }; + int[] multipleMessage = new int[autoRead.length]; + Arrays.fill(multipleMessage, 1); + long[] minimalWaitBetween = computeWaitAutoRead(autoRead); + testTrafficShapping0(sb, cb, false, true, false, false, autoRead, minimalWaitBetween, multipleMessage); + } + + @Test + @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS) + public void testAutoReadGlobalTrafficShapping(TestInfo testInfo) throws Throwable { + currentTestName = "TEST AUTO READ GLOBAL"; + currentTestRun = 0; + run(testInfo, new Runner() { + @Override + public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable { + testAutoReadGlobalTrafficShapping(serverBootstrap, bootstrap); + } + }); + } + + public void testAutoReadGlobalTrafficShapping(ServerBootstrap sb, Bootstrap cb) throws Throwable { + int[] autoRead = { 1, -1, -1, 1, -2, 0, 1, 0, -3, 0, 1, 2, 0 }; + int[] multipleMessage = new int[autoRead.length]; + Arrays.fill(multipleMessage, 1); + long[] minimalWaitBetween = computeWaitAutoRead(autoRead); + testTrafficShapping0(sb, cb, false, true, false, true, autoRead, minimalWaitBetween, multipleMessage); + } + + /** + * + * @param additionalExecutor + * shall the pipeline add the handler using an additional executor + * @param limitRead + * True to set Read Limit on Server side + * @param limitWrite + * True to set Write Limit on Client side + * @param globalLimit + * True to change Channel to Global TrafficShapping + * @param minimalWaitBetween + * time in ms that should be waited before getting the final result (note: for READ the values are + * right shifted once, the first value being 0) + * @param multipleMessage + * how many message to send at each step (for READ: the first should be 1, as the two last steps to + * ensure correct testing) + * @throws Throwable + */ + private static void testTrafficShapping0( + ServerBootstrap sb, Bootstrap cb, final boolean additionalExecutor, + final boolean limitRead, final boolean limitWrite, final boolean globalLimit, int[] autoRead, + long[] minimalWaitBetween, int[] multipleMessage) throws Throwable { + + currentTestRun++; + logger.info("TEST: " + currentTestName + " RUN: " + currentTestRun + + " Exec: " + additionalExecutor + " Read: " + limitRead + " Write: " + limitWrite + " Global: " + + globalLimit); + final ServerHandler sh = new ServerHandler(autoRead, multipleMessage); + Promise promise = group.next().newPromise(); + final ClientHandler ch = new ClientHandler(promise, minimalWaitBetween, multipleMessage, + autoRead); + + final AbstractTrafficShapingHandler handler; + if (limitRead) { + if (globalLimit) { + handler = new GlobalTrafficShapingHandler(groupForGlobal, 0, bandwidthFactor * messageSize, check); + } else { + handler = new ChannelTrafficShapingHandler(0, bandwidthFactor * messageSize, check); + } + } else if (limitWrite) { + if (globalLimit) { + handler = new GlobalTrafficShapingHandler(groupForGlobal, bandwidthFactor * messageSize, 0, check); + } else { + handler = new ChannelTrafficShapingHandler(bandwidthFactor * messageSize, 0, check); + } + } else { + handler = null; + } + + sb.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel c) throws Exception { + if (limitRead) { + c.pipeline().addLast(TRAFFIC, handler); + } + c.pipeline().addLast(sh); + } + }); + cb.handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel c) throws Exception { + if (limitWrite) { + c.pipeline().addLast(TRAFFIC, handler); + } + c.pipeline().addLast(ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect(sc.localAddress()).sync().channel(); + + int totalNb = 0; + for (int i = 1; i < multipleMessage.length; i++) { + totalNb += multipleMessage[i]; + } + Long start = TrafficCounter.milliSecondFromNano(); + int nb = multipleMessage[0]; + for (int i = 0; i < nb; i++) { + cc.write(cc.alloc().buffer().writeBytes(data)); + } + cc.flush(); + + promise.await(); + Long stop = TrafficCounter.milliSecondFromNano(); + assertTrue(promise.isSuccess(), "Error during execution of TrafficShapping: " + promise.cause()); + + float average = (totalNb * messageSize) / (float) (stop - start); + logger.info("TEST: " + currentTestName + " RUN: " + currentTestRun + + " Average of traffic: " + average + " compare to " + bandwidthFactor); + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + if (autoRead != null) { + // for extra release call in AutoRead + Thread.sleep(minimalms); + } + + if (autoRead == null && minimalWaitBetween != null) { + assertTrue(average <= maxfactor, + "Overall Traffic not ok since > " + maxfactor + ": " + average); + if (additionalExecutor) { + // Oio is not as good when using additionalExecutor + assertTrue(average >= 0.25, "Overall Traffic not ok since < 0.25: " + average); + } else { + assertTrue(average >= minfactor, + "Overall Traffic not ok since < " + minfactor + ": " + average); + } + } + if (handler != null && globalLimit) { + ((GlobalTrafficShapingHandler) handler).release(); + } + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class ClientHandler extends SimpleChannelInboundHandler { + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int step; + // first message will always be validated + private long currentLastTime = TrafficCounter.milliSecondFromNano(); + private final long[] minimalWaitBetween; + private final int[] multipleMessage; + private final int[] autoRead; + final Promise promise; + + ClientHandler(Promise promise, long[] minimalWaitBetween, int[] multipleMessage, + int[] autoRead) { + this.minimalWaitBetween = minimalWaitBetween; + this.multipleMessage = Arrays.copyOf(multipleMessage, multipleMessage.length); + this.promise = promise; + this.autoRead = autoRead; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + long lastTimestamp = 0; + loggerClient.debug("Step: " + step + " Read: " + in.readableBytes() / 8 + " blocks"); + while (in.isReadable()) { + lastTimestamp = in.readLong(); + multipleMessage[step]--; + } + if (multipleMessage[step] > 0) { + // still some message to get + return; + } + long minimalWait = minimalWaitBetween != null? minimalWaitBetween[step] : 0; + int ar = 0; + if (autoRead != null) { + if (step > 0 && autoRead[step - 1] != 0) { + ar = autoRead[step - 1]; + } + } + loggerClient.info("Step: " + step + " Interval: " + (lastTimestamp - currentLastTime) + " compareTo " + + minimalWait + " (" + ar + ')'); + assertTrue(lastTimestamp - currentLastTime >= minimalWait, + "The interval of time is incorrect:" + (lastTimestamp - currentLastTime) + " not> " + minimalWait); + currentLastTime = lastTimestamp; + step++; + if (multipleMessage.length > step) { + int nb = multipleMessage[step]; + for (int i = 0; i < nb; i++) { + channel.write(channel.alloc().buffer().writeBytes(data)); + } + channel.flush(); + } else { + promise.setSuccess(true); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + cause.printStackTrace(); + promise.setFailure(cause); + ctx.close(); + } + } + } + + private static class ServerHandler extends SimpleChannelInboundHandler { + private final int[] autoRead; + private final int[] multipleMessage; + volatile Channel channel; + volatile int step; + final AtomicReference exception = new AtomicReference(); + + ServerHandler(int[] autoRead, int[] multipleMessage) { + this.autoRead = autoRead; + this.multipleMessage = Arrays.copyOf(multipleMessage, multipleMessage.length); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channel = ctx.channel(); + } + + @Override + public void channelRead0(final ChannelHandlerContext ctx, ByteBuf in) throws Exception { + byte[] actual = new byte[in.readableBytes()]; + int nb = actual.length / messageSize; + loggerServer.info("Step: " + step + " Read: " + nb + " blocks"); + in.readBytes(actual); + long timestamp = TrafficCounter.milliSecondFromNano(); + int isAutoRead = 0; + int laststep = step; + for (int i = 0; i < nb; i++) { + multipleMessage[step]--; + if (multipleMessage[step] == 0) { + // setAutoRead test + if (autoRead != null) { + isAutoRead = autoRead[step]; + } + step++; + } + } + if (laststep != step) { + // setAutoRead test + if (autoRead != null && isAutoRead != 2) { + if (isAutoRead != 0) { + loggerServer.info("Step: " + step + " Set AutoRead: " + (isAutoRead > 0)); + channel.config().setAutoRead(isAutoRead > 0); + } else { + loggerServer.info("Step: " + step + " AutoRead: NO"); + } + } + } + Thread.sleep(10); + loggerServer.debug("Step: " + step + " Write: " + nb); + for (int i = 0; i < nb; i++) { + channel.write(Unpooled.copyLong(timestamp)); + } + channel.flush(); + if (laststep != step) { + // setAutoRead test + if (isAutoRead != 0) { + if (isAutoRead < 0) { + final int exactStep = step; + long wait = isAutoRead == -1? minimalms : stepms + minimalms; + if (isAutoRead == -3) { + wait = stepms * 3; + } + executor.schedule(new Runnable() { + @Override + public void run() { + loggerServer.info("Step: " + exactStep + " Reset AutoRead"); + channel.config().setAutoRead(true); + } + }, wait, TimeUnit.MILLISECONDS); + } else { + if (isAutoRead > 1) { + loggerServer.debug("Step: " + step + " Will Set AutoRead: True"); + final int exactStep = step; + executor.schedule(new Runnable() { + @Override + public void run() { + loggerServer.info("Step: " + exactStep + " Set AutoRead: True"); + channel.config().setAutoRead(true); + } + }, stepms + minimalms, TimeUnit.MILLISECONDS); + } + } + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + cause.printStackTrace(); + ctx.close(); + } + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/WriteBeforeRegisteredTest.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/WriteBeforeRegisteredTest.java new file mode 100644 index 0000000..9127605 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/WriteBeforeRegisteredTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.SocketChannel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.TimeUnit; + +public class WriteBeforeRegisteredTest extends AbstractClientSocketTest { + + @Test + @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS) + public void testWriteBeforeConnect(TestInfo testInfo) throws Throwable { + run(testInfo, new Runner() { + @Override + public void run(Bootstrap bootstrap) throws Throwable { + testWriteBeforeConnect(bootstrap); + } + }); + } + + public void testWriteBeforeConnect(Bootstrap cb) throws Throwable { + TestHandler h = new TestHandler(); + SocketChannel ch = null; + try { + ch = (SocketChannel) cb.handler(h).connect(newSocketAddress()).channel(); + ch.writeAndFlush(Unpooled.wrappedBuffer(new byte[] { 1 })); + } finally { + if (ch != null) { + ch.close(); + } + } + } + + private static class TestHandler extends ChannelInboundHandlerAdapter { + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + } + } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/package-info.java b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/package-info.java new file mode 100644 index 0000000..b4f3279 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/transport/socket/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Test suite classes for socket + */ +package io.netty.testsuite.transport.socket; diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/util/TestUtils.java b/netty-testsuite/src/main/java/io/netty/testsuite/util/TestUtils.java new file mode 100644 index 0000000..f1192de --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/util/TestUtils.java @@ -0,0 +1,273 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ +package io.netty.testsuite.util; + +import io.netty.util.CharsetUtil; +import io.netty.util.internal.ObjectUtil; +import io.netty.util.internal.SuppressJava6Requirement; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.jupiter.api.TestInfo; +import org.tukaani.xz.LZMA2Options; +import org.tukaani.xz.XZOutputStream; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.reflect.Method; +import java.nio.channels.Channel; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import javax.management.MBeanServer; + +public final class TestUtils { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(TestUtils.class); + + private static final Method hotspotMXBeanDumpHeap; + private static final Object hotspotMXBean; + + private static final long DUMP_PROGRESS_LOGGING_INTERVAL = TimeUnit.SECONDS.toNanos(5); + + static { + // Retrieve the hotspot MXBean and its class if available. + Object mxBean; + Method mxBeanDumpHeap; + try { + Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + mxBean = ManagementFactory.newPlatformMXBeanProxy( + server, "com.sun.management:type=HotSpotDiagnostic", clazz); + mxBeanDumpHeap = clazz.getMethod("dumpHeap", String.class, boolean.class); + } catch (Exception ignored) { + mxBean = null; + mxBeanDumpHeap = null; + } + + hotspotMXBean = mxBean; + hotspotMXBeanDumpHeap = mxBeanDumpHeap; + } + + /** + * Return {@code true} if SCTP is supported by the running os. + * + */ + public static boolean isSctpSupported() { + String os = System.getProperty("os.name").toLowerCase(Locale.US); + if ("unix".equals(os) || "linux".equals(os) || "sun".equals(os) || "solaris".equals(os)) { + try { + // Try to open a SCTP Channel, by using reflection to make it compile also on + // operation systems that not support SCTP like OSX and Windows + Class sctpChannelClass = Class.forName("com.sun.nio.sctp.SctpChannel"); + Channel channel = (Channel) sctpChannelClass.getMethod("open").invoke(null); + try { + channel.close(); + } catch (IOException e) { + // ignore + } + } catch (UnsupportedOperationException e) { + // This exception may get thrown if the OS does not have + // the shared libs installed. + System.out.print("Not supported: " + e.getMessage()); + return false; + } catch (Throwable t) { + if (!(t instanceof IOException)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Returns the method name of the current test. + */ + @SuppressJava6Requirement(reason = "Test only") + public static String testMethodName(TestInfo testInfo) { + String testMethodName = testInfo.getTestMethod().map(new Function() { + @Override + public String apply(Method method) { + return method.getName(); + } + }).orElse("[unknown method]"); + if (testMethodName.contains("[")) { + testMethodName = testMethodName.substring(0, testMethodName.indexOf('[')); + } + return testMethodName; + } + + public static void dump(String filenamePrefix) throws IOException { + + ObjectUtil.checkNotNull(filenamePrefix, "filenamePrefix"); + + final String timestamp = timestamp(); + final File heapDumpFile = new File(filenamePrefix + '.' + timestamp + ".hprof"); + if (heapDumpFile.exists()) { + if (!heapDumpFile.delete()) { + throw new IOException("Failed to remove the old heap dump: " + heapDumpFile); + } + } + + final File threadDumpFile = new File(filenamePrefix + '.' + timestamp + ".threads"); + if (threadDumpFile.exists()) { + if (!threadDumpFile.delete()) { + throw new IOException("Failed to remove the old thread dump: " + threadDumpFile); + } + } + + dumpHeap(heapDumpFile); + dumpThreads(threadDumpFile); + } + + public static void compressHeapDumps() throws IOException { + final File[] files = new File(System.getProperty("user.dir")).listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".hprof"); + } + }); + if (files == null) { + logger.warn("failed to find heap dump due to I/O error!"); + return; + } + + final byte[] buf = new byte[65536]; + final LZMA2Options options = new LZMA2Options(LZMA2Options.PRESET_DEFAULT); + + for (File file: files) { + final String filename = file.toString(); + final String xzFilename = filename + ".xz"; + final long fileLength = file.length(); + + logger.info("Compressing the heap dump: {}", xzFilename); + + long lastLogTime = System.nanoTime(); + long counter = 0; + + InputStream in = null; + OutputStream out = null; + try { + in = new FileInputStream(filename); + out = new XZOutputStream(new FileOutputStream(xzFilename), options); + for (;;) { + int readBytes = in.read(buf); + if (readBytes < 0) { + break; + } + if (readBytes == 0) { + continue; + } + + out.write(buf, 0, readBytes); + counter += readBytes; + + long currentTime = System.nanoTime(); + if (currentTime - lastLogTime > DUMP_PROGRESS_LOGGING_INTERVAL) { + logger.info("Compressing the heap dump: {} ({}%)", + xzFilename, counter * 100 / fileLength); + lastLogTime = currentTime; + } + } + out.close(); + in.close(); + } catch (Throwable t) { + logger.warn("Failed to compress the heap dump: {}", xzFilename, t); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + // Ignore. + } + } + if (out != null) { + try { + out.close(); + } catch (IOException ignored) { + // Ignore. + } + } + } + + // Delete the uncompressed dump in favor of the compressed one. + if (!file.delete()) { + logger.warn("Failed to delete the uncompressed heap dump: {}", filename); + } + } + } + + private static String timestamp() { + return new SimpleDateFormat("HHmmss.SSS").format(new Date()); + } + + private static void dumpHeap(File file) { + if (hotspotMXBean == null) { + logger.warn("Can't dump heap: HotSpotDiagnosticMXBean unavailable"); + return; + } + + final String filename = file.toString(); + logger.info("Dumping heap: {}", filename); + try { + hotspotMXBeanDumpHeap.invoke(hotspotMXBean, filename, true); + } catch (Exception e) { + logger.warn("Failed to dump heap: {}", filename, e); + } + } + + private static void dumpThreads(File file) { + final String filename = file.toString(); + OutputStream out = null; + try { + logger.info("Dumping threads: {}", filename); + final StringBuilder buf = new StringBuilder(8192); + try { + for (ThreadInfo info : ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)) { + buf.append(info); + } + buf.append('\n'); + } catch (UnsupportedOperationException ignored) { + logger.warn("Can't dump threads: ThreadMXBean.dumpAllThreads() unsupported"); + return; + } + + out = new FileOutputStream(file); + out.write(buf.toString().getBytes(CharsetUtil.UTF_8)); + } catch (Exception e) { + logger.warn("Failed to dump threads: {}", filename, e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException ignored) { + // Ignore. + } + } + } + } + + private TestUtils() { } +} diff --git a/netty-testsuite/src/main/java/io/netty/testsuite/util/package-info.java b/netty-testsuite/src/main/java/io/netty/testsuite/util/package-info.java new file mode 100644 index 0000000..178aad1 --- /dev/null +++ b/netty-testsuite/src/main/java/io/netty/testsuite/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you 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: + * + * https://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. + */ + +/** + * Utility classes that help testing a network application. + */ +package io.netty.testsuite.util; diff --git a/netty-util/src/main/java/module-info.java b/netty-util/src/main/java/module-info.java index d066d4c..f4ee339 100644 --- a/netty-util/src/main/java/module-info.java +++ b/netty-util/src/main/java/module-info.java @@ -6,6 +6,7 @@ module org.xbib.io.netty.util { org.xbib.io.netty.buffer, org.xbib.io.netty.channel, org.xbib.io.netty.channel.epoll, + org.xbib.io.netty.channel.sctp, org.xbib.io.netty.channel.unix, org.xbib.io.netty.handler, org.xbib.io.netty.handler.codec, @@ -16,6 +17,7 @@ module org.xbib.io.netty.util { org.xbib.io.netty.handler.codec.protobuf, org.xbib.io.netty.handler.codec.quic, org.xbib.io.netty.handler.codec.rtsp, + org.xbib.io.netty.handler.codec.sctp, org.xbib.io.netty.handler.codec.spdy, org.xbib.io.netty.handler.ssl, org.xbib.io.netty.resolver; @@ -23,6 +25,7 @@ module org.xbib.io.netty.util { org.xbib.io.netty.buffer, org.xbib.io.netty.channel, org.xbib.io.netty.channel.epoll, + org.xbib.io.netty.channel.sctp, org.xbib.io.netty.channel.unix, org.xbib.io.netty.handler, org.xbib.io.netty.handler.codec, @@ -33,6 +36,7 @@ module org.xbib.io.netty.util { org.xbib.io.netty.handler.codec.protobuf, org.xbib.io.netty.handler.codec.quic, org.xbib.io.netty.handler.codec.rtsp, + org.xbib.io.netty.handler.codec.sctp, org.xbib.io.netty.handler.codec.spdy, org.xbib.io.netty.handler.ssl, org.xbib.io.netty.resolver; diff --git a/settings.gradle b/settings.gradle index f1d74d8..8be824a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,13 +23,13 @@ dependencyResolutionManagement { library('lz4', 'org.lz4', 'lz4-java').version('1.8.0') library('lzf', 'com.ning', 'compress-lzf').version('1.1.2') library('zstd', 'com.github.luben', 'zstd-jni').version('1.5.5-11') - library('protobuf', 'com.google.protobuf', 'protobuf-java').version('4.0.0-rc-2') library('brotli4j-native-linux-x8664', 'com.aayushatharva.brotli4j', 'native-linux-x86_64').versionRef('brotli4j') library('brotli4j-native-linux-aarch64', 'com.aayushatharva.brotli4j', 'native-linux-aarch64').versionRef('brotli4j') library('brotli4j-native-linux-riscv64', 'com.aayushatharva.brotli4j', 'native-linux-riscv64').versionRef('brotli4j') library('brotli4j-native-osx-x8664', 'com.aayushatharva.brotli4j', 'native-osx-x86_64').versionRef('brotli4j') library('brotli4j-native-osx-aarch64', 'com.aayushatharva.brotli4j', 'native-osx-aarch64').versionRef('brotli4j') library('brotli4j-native-windows-x8664', 'com.aayushatharva.brotli4j', 'native-windows-x86_64').versionRef('brotli4j') + library('protobuf', 'com.google.protobuf', 'protobuf-java').version('4.0.0-rc-2') } testLibs { version('junit', '5.10.1') @@ -50,6 +50,8 @@ dependencyResolutionManagement { library('reflections', 'org.reflections', 'reflections').version('0.10.2') library('amazonCorrettoCrypt', 'software.amazon.cryptools', 'AmazonCorrettoCryptoProvider').version('2.3.2') library('commons-compress', 'org.apache.commons', 'commons-compress').version('1.25.0') + library('xz-tools', 'org.tukaani', 'xz').version('1.9') + library('rerunner-jupiter', 'io.github.artsok', 'rerunner-jupiter').version('2.1.6') } } } @@ -58,6 +60,8 @@ include 'netty-buffer' include 'netty-bzip2' include 'netty-channel' include 'netty-channel-epoll' +include 'netty-channel-epoll-native' +include 'netty-channel-sctp' include 'netty-channel-unix' include 'netty-handler' include 'netty-handler-codec' @@ -69,11 +73,13 @@ include 'netty-handler-codec-protobuf' include 'netty-handler-codec-quic' include 'netty-handler-codec-quic-native' include 'netty-handler-codec-rtsp' +include 'netty-handler-codec-sctp' include 'netty-handler-codec-spdy' include 'netty-handler-ssl' include 'netty-internal-tcnative' include 'netty-jctools' include 'netty-resolver' include 'netty-tcnative-boringssl-static' +include 'netty-testsuite' include 'netty-util' include 'netty-zlib'