From 178624499d28371701cc007730918fc6ca1fb1e5 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sun, 9 Jul 2023 23:11:34 +0700 Subject: [PATCH 01/46] ssl --- .../EzyAbstractServerBootstrapBuilder.java | 8 +++++- .../tvd12/ezyfoxserver/constant/SslType.java | 6 +++++ .../setting/EzySimpleSocketSetting.java | 5 ++++ .../setting/EzySocketSetting.java | 3 +++ .../src/main/resources/ezy-settings-1.0.0.xsd | 13 +++++++++- .../src/main/resources/ezy-settings.xml | 1 + .../nio/EzyNioServerBootstrap.java | 1 + .../nio/EzySocketServerBootstrap.java | 26 +++++++++++++++++-- 8 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java index 5038e14f..1f3bc048 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java @@ -4,6 +4,7 @@ import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.EzyServerBootstrap; import com.tvd12.ezyfoxserver.config.EzyConfig; +import com.tvd12.ezyfoxserver.constant.SslType; import com.tvd12.ezyfoxserver.context.EzyServerContext; import com.tvd12.ezyfoxserver.setting.*; import com.tvd12.ezyfoxserver.ssl.EzySslContextInitializer; @@ -45,7 +46,12 @@ protected EzyServerContext newServerContext(EzyServer server) { } protected SSLContext newSslContext(EzySslConfigSetting sslConfig) { - if (getWebsocketSetting().isSslActive()) { + EzySocketSetting socketSetting = getSocketSetting(); + EzyWebSocketSetting webSocketSetting = getWebsocketSetting(); + boolean activeSslForSocket = socketSetting.isSslActive() + && socketSetting.getSslType() == SslType.L4; + boolean activeSslForWebsocket = webSocketSetting.isSslActive(); + if (activeSslForSocket || activeSslForWebsocket) { return newSslContextInitializer(sslConfig).init(); } return null; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java new file mode 100644 index 00000000..a61778e8 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java @@ -0,0 +1,6 @@ +package com.tvd12.ezyfoxserver.constant; + +public enum SslType { + L4, + L7 +} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java index 709b91a7..1aa3150e 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java @@ -1,5 +1,6 @@ package com.tvd12.ezyfoxserver.setting; +import com.tvd12.ezyfoxserver.constant.SslType; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -19,6 +20,9 @@ public class EzySimpleSocketSetting extends EzyAbstractSocketSetting implements EzySocketSetting { + @XmlElement(name = "ssl-type") + protected SslType sslType; + @XmlElement(name = "max-request-size") protected int maxRequestSize; @@ -31,6 +35,7 @@ public class EzySimpleSocketSetting public EzySimpleSocketSetting() { super(); setPort(3005); + setSslType(SslType.L7); setMaxRequestSize(4096); setWriterThreadPoolSize(8); setCodecCreator("com.tvd12.ezyfox.codec.MsgPackCodecCreator"); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 0666bdcd..02bd160d 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java @@ -1,6 +1,9 @@ package com.tvd12.ezyfoxserver.setting; +import com.tvd12.ezyfoxserver.constant.SslType; + public interface EzySocketSetting extends EzyBaseSocketSetting { + SslType getSslType(); boolean isTcpNoDelay(); diff --git a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd index acd6c327..e863e1d8 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd +++ b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd @@ -63,6 +63,7 @@ + @@ -75,7 +76,6 @@ - @@ -97,6 +97,17 @@ + + + + + + + + + + + diff --git a/ezyfox-server-core/src/main/resources/ezy-settings.xml b/ezyfox-server-core/src/main/resources/ezy-settings.xml index 3f49326f..e7c207cc 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-settings.xml @@ -27,6 +27,7 @@
0.0.0.0
true true + L7 true 4096 8 diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java index e36d2e98..72f53c0c 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java @@ -130,6 +130,7 @@ private void startUserRemovalHandlingLoopHandlers() throws Exception { private EzySocketServerBootstrap newSocketServerBootstrap() { return EzySocketServerBootstrap.builder() .serverContext(context) + .sslContext(sslContext) .socketDataReceiver(socketDataReceiver) .handlerGroupManager(handlerGroupManager) .sessionTicketsQueue(socketSessionTicketsQueue) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index aee2353d..018c5f40 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -1,5 +1,6 @@ package com.tvd12.ezyfoxserver.nio; +import com.tvd12.ezyfoxserver.constant.SslType; import com.tvd12.ezyfoxserver.nio.constant.EzyNioThreadPoolSizes; import com.tvd12.ezyfoxserver.nio.socket.*; import com.tvd12.ezyfoxserver.setting.EzySocketSetting; @@ -8,6 +9,7 @@ import com.tvd12.ezyfoxserver.socket.EzySocketWriter; import com.tvd12.ezyfoxserver.socket.EzySocketWritingLoopHandler; +import javax.net.ssl.SSLContext; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; @@ -23,12 +25,13 @@ public class EzySocketServerBootstrap extends EzyAbstractSocketServerBootstrap { private Selector acceptSelector; private ServerSocket serverSocket; private ServerSocketChannel serverSocketChannel; - + private final SSLContext sslContext; private EzySocketEventLoopHandler readingLoopHandler; private EzySocketEventLoopHandler socketAcceptanceLoopHandler; public EzySocketServerBootstrap(Builder builder) { super(builder); + this.sslContext = builder.sslContext; } public static Builder builder() { @@ -63,12 +66,24 @@ private void newAndConfigServerSocketChannel() throws Exception { } private void getBindAndConfigServerSocket() throws Exception { - this.serverSocket = serverSocketChannel.socket(); + this.serverSocket = createServerSocket(); this.serverSocket.setReuseAddress(true); this.serverSocket.bind(new InetSocketAddress(getSocketAddress(), getSocketPort())); this.serverSocketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT); } + private ServerSocket createServerSocket() throws Exception { + EzySocketSetting socketSetting = getSocketSetting(); + boolean sslActive = socketSetting.isSslActive(); + SslType sslType = socketSetting.getSslType(); + if (sslActive && sslType == SslType.L4) { + return sslContext + .getServerSocketFactory() + .createServerSocket(getSocketPort()); + } + return serverSocketChannel.socket(); + } + private void startSocketHandlers() throws Exception { EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); writingLoopHandler = newWritingLoopHandler(); @@ -155,6 +170,13 @@ private EzySocketSetting getSocketSetting() { public static class Builder extends EzyAbstractSocketServerBootstrap.Builder { + private SSLContext sslContext; + + public Builder sslContext(SSLContext sslContext) { + this.sslContext = sslContext; + return this; + } + @Override public EzySocketServerBootstrap build() { return new EzySocketServerBootstrap(this); From 21b8b6f589b3546cecb1efc2c947eb056838b731 Mon Sep 17 00:00:00 2001 From: Ta Van Dung Date: Mon, 10 Jul 2023 21:36:03 +0700 Subject: [PATCH 02/46] update (#111) --- .../settings/ssl-config.properties | 2 +- .../setting/EzySimpleUdpSetting.java | 30 +++++++++++++------ .../setting/EzySocketSettingBuilder.java | 10 +++++++ .../ezyfoxserver/setting/EzyUdpSetting.java | 10 ++++++- .../setting/EzyUdpSettingBuilder.java | 29 +++++++++++++++--- .../ssl/EzySimpleSslContextFactory.java | 5 +++- .../setting/EzySettingsBuilderTest.java | 2 -- .../embedded/test/HelloEmbeddedServer3.java | 1 - .../nio/EzySocketServerBootstrap.java | 20 +++++++++---- 9 files changed, 85 insertions(+), 24 deletions(-) diff --git a/ezyfox-server-core/settings/ssl-config.properties b/ezyfox-server-core/settings/ssl-config.properties index 13480ef7..172811f4 100644 --- a/ezyfox-server-core/settings/ssl-config.properties +++ b/ezyfox-server-core/settings/ssl-config.properties @@ -1,3 +1,3 @@ -ssl.keystore="ssl/ssl-key-store.txt" +ssl.keystore=ssl/ssl-keystore.txt ssl.keystore_password=ssl/ssl-keystore-password.txt ssl.certificate_password=ssl/ssl-certificate-password.txt diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleUdpSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleUdpSetting.java index 5e4cb090..62b41d7d 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleUdpSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleUdpSetting.java @@ -1,23 +1,32 @@ package com.tvd12.ezyfoxserver.setting; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; +import java.util.HashMap; +import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import java.util.Map; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; @Setter @Getter @ToString @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "udp") -public class EzySimpleUdpSetting - extends EzyAbstractSocketSetting - implements EzyUdpSetting { +public class EzySimpleUdpSetting implements EzyUdpSetting { + + @XmlElement(name = "port") + protected int port; + + @XmlElement(name = "address") + protected String address; + + @XmlElement(name = "active") + protected boolean active; @XmlElement(name = "max-request-size") protected int maxRequestSize; @@ -31,16 +40,19 @@ public class EzySimpleUdpSetting public EzySimpleUdpSetting() { super(); setPort(2611); + setAddress("0.0.0.0"); setActive(false); setMaxRequestSize(1024); setChannelPoolSize(16); setHandlerThreadPoolSize(5); - setCodecCreator("com.tvd12.ezyfox.codec.MsgPackCodecCreator"); } @Override public Map toMap() { - Map map = super.toMap(); + Map map = new HashMap<>(); + map.put("port", port); + map.put("address", address); + map.put("active", active); map.put("maxRequestSize", maxRequestSize); map.put("channelPoolSize", channelPoolSize); map.put("handlerThreadPoolSize", handlerThreadPoolSize); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java index 17a0b915..8e092f0a 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java @@ -1,21 +1,30 @@ package com.tvd12.ezyfoxserver.setting; +import com.tvd12.ezyfoxserver.constant.SslType; + public class EzySocketSettingBuilder extends EzyAbstractSocketSettingBuilder< EzySimpleSocketSetting, EzySocketSettingBuilder> { + protected SslType sslType; protected int maxRequestSize; protected boolean tcpNoDelay; protected int writerThreadPoolSize; public EzySocketSettingBuilder() { this.port = 3005; + this.sslType = SslType.L7; this.maxRequestSize = 32768; this.writerThreadPoolSize = 8; this.codecCreator = "com.tvd12.ezyfox.codec.MsgPackCodecCreator"; } + public EzySocketSettingBuilder sslType(SslType sslType) { + this.sslType = sslType; + return this; + } + public EzySocketSettingBuilder maxRequestSize(int maxRequestSize) { this.maxRequestSize = maxRequestSize; return this; @@ -34,6 +43,7 @@ public EzySocketSettingBuilder writerThreadPoolSize(int writerThreadPoolSize) { @Override protected EzySimpleSocketSetting newSetting() { EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslType(sslType); setting.setTcpNoDelay(tcpNoDelay); setting.setSslActive(sslActive); setting.setMaxRequestSize(maxRequestSize); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSetting.java index 77a3f92b..0043898d 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSetting.java @@ -1,6 +1,14 @@ package com.tvd12.ezyfoxserver.setting; -public interface EzyUdpSetting extends EzyBaseSocketSetting { +import com.tvd12.ezyfox.util.EzyToMap; + +public interface EzyUdpSetting extends EzyToMap { + + int getPort(); + + String getAddress(); + + boolean isActive(); int getMaxRequestSize(); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSettingBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSettingBuilder.java index 06e07b37..ce473bc7 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSettingBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzyUdpSettingBuilder.java @@ -1,19 +1,37 @@ package com.tvd12.ezyfoxserver.setting; -public class EzyUdpSettingBuilder extends EzyAbstractSocketSettingBuilder< - EzySimpleUdpSetting, EzyUdpSettingBuilder> { +import com.tvd12.ezyfox.builder.EzyBuilder; +public class EzyUdpSettingBuilder implements EzyBuilder { + protected int port; + protected String address; + protected boolean active; protected int maxRequestSize; protected int channelPoolSize; protected int handlerThreadPoolSize; public EzyUdpSettingBuilder() { this.port = 2611; + this.address = "0.0.0.0"; this.active = false; this.maxRequestSize = 1024; this.channelPoolSize = 16; this.handlerThreadPoolSize = 5; - this.codecCreator = "com.tvd12.ezyfox.codec.MsgPackCodecCreator"; + } + + public EzyUdpSettingBuilder port(int port) { + this.port = port; + return this; + } + + public EzyUdpSettingBuilder address(String address) { + this.address = address; + return this; + } + + public EzyUdpSettingBuilder active(boolean active) { + this.active = active; + return this; } public EzyUdpSettingBuilder maxRequestSize(int maxRequestSize) { @@ -32,8 +50,11 @@ public EzyUdpSettingBuilder handlerThreadPoolSize(int handlerThreadPoolSize) { } @Override - public EzySimpleUdpSetting newSetting() { + public EzySimpleUdpSetting build() { EzySimpleUdpSetting p = new EzySimpleUdpSetting(); + p.setPort(port); + p.setAddress(address); + p.setActive(active); p.setMaxRequestSize(maxRequestSize); p.setChannelPoolSize(channelPoolSize); p.setHandlerThreadPoolSize(handlerThreadPoolSize); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java index 19d35610..6e7e3fbf 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java @@ -77,7 +77,10 @@ protected char[] getPassword(String file) throws Exception { InputStream stream = newInputStreamLoader().load(file); char[] answer; try { - answer = newInputStreamReader().readChars(stream, "UTF-8"); + answer = newInputStreamReader() + .readString(stream, "UTF-8") + .trim() + .toCharArray(); } finally { stream.close(); } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySettingsBuilderTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySettingsBuilderTest.java index dc221919..4eacb608 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySettingsBuilderTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySettingsBuilderTest.java @@ -35,7 +35,6 @@ public void test() { .active(true) .address("2.2.2.2") .channelPoolSize(3) - .codecCreator(TestCodecCreator.class) .handlerThreadPoolSize(3) .maxRequestSize(2048) .port(23456) @@ -119,7 +118,6 @@ public void test() { udpSetting = settings.getUdp(); assertTrue(udpSetting.isActive()); assertEquals(udpSetting.getAddress(), "2.2.2.2"); - assertEquals(udpSetting.getCodecCreator(), TestCodecCreator.class.getName()); assertEquals(udpSetting.getMaxRequestSize(), 2048); assertEquals(udpSetting.getPort(), 23456); assertEquals(udpSetting.getChannelPoolSize(), 3); diff --git a/ezyfox-server-embedded/src/test/java/com/tvd12/ezyfoxserver/embedded/test/HelloEmbeddedServer3.java b/ezyfox-server-embedded/src/test/java/com/tvd12/ezyfoxserver/embedded/test/HelloEmbeddedServer3.java index d1f35c28..13d0b075 100644 --- a/ezyfox-server-embedded/src/test/java/com/tvd12/ezyfoxserver/embedded/test/HelloEmbeddedServer3.java +++ b/ezyfox-server-embedded/src/test/java/com/tvd12/ezyfoxserver/embedded/test/HelloEmbeddedServer3.java @@ -99,7 +99,6 @@ public static void main(String[] args) throws Exception { .active(true) // active or not .address("0.0.0.0") // set loopback IP .channelPoolSize(16) // set number of udp channel for socket writing, default 16 - .codecCreator(MsgPackCodecCreator.class) // encoder/decoder creator, default MsgPackCodecCreator .handlerThreadPoolSize(5) // set number of handler's thread, default 5 .maxRequestSize(1024) // set max request's size .port(2611) // set listen port diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 018c5f40..e868fcfd 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -10,6 +10,8 @@ import com.tvd12.ezyfoxserver.socket.EzySocketWritingLoopHandler; import javax.net.ssl.SSLContext; + +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; @@ -67,21 +69,29 @@ private void newAndConfigServerSocketChannel() throws Exception { private void getBindAndConfigServerSocket() throws Exception { this.serverSocket = createServerSocket(); - this.serverSocket.setReuseAddress(true); - this.serverSocket.bind(new InetSocketAddress(getSocketAddress(), getSocketPort())); this.serverSocketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT); } private ServerSocket createServerSocket() throws Exception { + ServerSocket server; EzySocketSetting socketSetting = getSocketSetting(); boolean sslActive = socketSetting.isSslActive(); SslType sslType = socketSetting.getSslType(); if (sslActive && sslType == SslType.L4) { - return sslContext + server = sslContext .getServerSocketFactory() - .createServerSocket(getSocketPort()); + .createServerSocket( + getSocketPort(), + 0, + InetAddress.getByName(getSocketAddress()) + ); + server.setReuseAddress(true); + } else { + server = serverSocketChannel.socket(); + server.setReuseAddress(true); + server.bind(new InetSocketAddress(getSocketAddress(), getSocketPort())); } - return serverSocketChannel.socket(); + return server; } private void startSocketHandlers() throws Exception { From 63e6ce3a30b30a8dcfa9ef9be443e69581ed2d64 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 11 Jul 2023 17:13:35 +0700 Subject: [PATCH 03/46] can handshake ssl --- .../settings/ssl/ssl-key-store.txt | Bin 3006 -> 0 bytes .../settings/ssl/ssl-keystore.txt | Bin 0 -> 2055 bytes .../setting/EzySimpleSocketSetting.java | 1 + .../ssl/EzySimpleSslContextFactory.java | 44 ++-- .../EzySimpleSslContextFactoryBuilder.java | 16 +- .../nio/EzySocketServerBootstrap.java | 46 ++-- .../EzyHandlerGroupBuilderFactoryImpl.java | 16 +- .../nio/handler/EzyAbstractHandlerGroup.java | 12 +- .../nio/socket/EzyNioSocketAcceptor.java | 221 +++++++++++++++++- .../nio/socket/EzySocketDataReceiver.java | 12 +- .../handler/EzyAbstractHandlerGroupTest.java | 2 +- 11 files changed, 282 insertions(+), 88 deletions(-) delete mode 100644 ezyfox-server-core/settings/ssl/ssl-key-store.txt create mode 100644 ezyfox-server-core/settings/ssl/ssl-keystore.txt diff --git a/ezyfox-server-core/settings/ssl/ssl-key-store.txt b/ezyfox-server-core/settings/ssl/ssl-key-store.txt deleted file mode 100644 index 27c1b25b2babeaa3fa813540cd82ea7eca554148..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3006 zcmdT`-%k`*6rR}ymh75ESi~(NEFixHXZW!q0)og73&9mp5fH)Mfkk8&cL$ek`C;mN zW1FTAeK4&_`_?o~W7D)66Pq+?G|?taZEMt*#>e_E^!q&1p-oI`(l$-WcfRx8bBCFG z&%O8DeRk{Sv)`d8io-HhaTzN!i%aJXMNtd!#7S5cG@J%|?!8&=x-2zAt##z8YNnE5 z#RnE43Sc2v5Y}H68gs*stXF{Z3oEDU;mcvu8+m~f@IA1F{?lMVwYY+zi&=K8hQr8# zO%H<3O8nI@d;_d!1=LtFE#ZxvRfwHW-@ZBmzXogBFFna2@uT9ZW3v;Y);a1uqKhwx z7YlVCKO=0fZ5v8`DoYE8(&GY)F&Q1Cu$k)$z{>O0RA9%NVB&5WYHw^bnlu3 zgy-Q_+PDCMT(~unrj<3cO1xHL-CtkYv-L0{%xkthSJn$(D!Nk~m(^P&S~%ev-F#g; zVsZXA7uf@A-v_E6I8?nkBLoCzbod19@w=d7u+ClFcK+THaFaDXe%}kT;p&Fu!ksJk z+d&AX_uDT)eLbKD*Q3>X9RzTcE5P@^5(Uj-%d1M}SVN1p4+o%)E9(f7xh77B>TqNX zoPyNcShCx8oRTc$1}2gj=>S#r{%Jd?Q;*Nmt%8t^YxLqp0DLG z=<<1v8S&U$Y-X`OIjVdFwjsusG|ZhhW){=2c*I~p3v735ty)mur?oWeO|9ep zoJPHMudX*XTY>-4Tqa+*9;idkK}8#IbStt(Q`{avu5yqo$6W!GKm4>~48CQWyOOJG zJw5}OJ^*UZ%LKEuoQmeB+TcSK+>M9*VZO1x8njuQ&~P|aMSbCzut%KTig8|3Z@<}j z=d%=d&n!RG1rs>o&e^Tt)a%e?EDpN^cJ%%mfufEFSEnWs8NYae18}X`y(uV8@Q`fh z#q@%awrckTPv6Vc?izVt_7?c%&PUhbSmC1IT^X#=f4y=8ItgQfm^gqt#;RJQ9D~cs zFtuQHm=n&1?dF39=PI~=eZPqO%-1-3j&oQd-o}iwGP9raD}`x#q%R^po0UUnePW3b zFjEKZ7KG1e1h3wGOM<)lIb@#ukj5_w_Sy=-z2LMz?^b!-WWes1_RQto+!v^)`2;^jf)`Q z!0nH~)^%3HL~iBCQ`^&L?tEPG;|I`!1##V#@SY>zT+WO`M7Z}3Nl#bZ{;r3g@RG7& zx*+}2(%$B6tElCDv0~kivEg>Nbnz`!yg8ZH6qoljtg)*H42&>am8|(z+9Yh%?-gl{ zHN&TppwMw;nxo_k1)Q)A_hBBmqgG}iUCdYH?yWyp@MdmFhelR^e*eEO;M+8x6!0xQ z@tB#IKf7%H{p#J)tZi2BCRA?*{^jcZk4@wyOLzZvEMd+`&}_oJKEa=C@$}<&;5__U z1?(8hD=g@Mc=92S2N3!GQZY{wkm2O%0Vc)S%zmD3!9?j_Rc;3!d~}l~v6MCD2>+L* z$J*0vB}+|c*?9gF=JOMDMG_omVFuRA#e?r%wIu{E1AwP#C(A)-D3^!kk0?C5Lz+Ll LXC04iN8P^xIw1S$ diff --git a/ezyfox-server-core/settings/ssl/ssl-keystore.txt b/ezyfox-server-core/settings/ssl/ssl-keystore.txt new file mode 100644 index 0000000000000000000000000000000000000000..78ab0217e62d5cfc331a0531d656791ee91021ba GIT binary patch literal 2055 zcmV+i2>AE@?f&fm0006200031000314P|NuTB4L$|ZyijR z^&FzP=Cfnq<1w4h`LKt>2>5Cf;Ao?v*ZbF!^inv0dJ!b>4w?XPj_oq!=edCBdyz>X z0j{}GrSIK#=)cewB}=FHU$_TqMcv3TX_oJtX0@639w~#|iH5&6+|xu* zK4$Qo-smEfcLDL4l~E%%9@>PjG%jWP35EGdII4bT%d>NdE)-gmLJjg#jX%biqIBHA;dV^w){={?mSTBcZ_=wu6 z&Lb9lpZ*(Bdch|q-SCbk^>NMJr67VbJ(cs+%q*HYqW7T=E~k`_Us7JAe3Fd0p^Gj2 zx(j9EZ27v}!yWE4@WOs)d&!{wZo=33&oovNh~<)xdr+j!A*%RgG3hX_xD#I|dtZ|9 zOK`hWnXDAxWKi|qnYBTEa;_#YNwO;RVsf`6QOCZ2)y!y$Yu@!tI8O~C#RvCWlln>F zYi@%)NJ{>@>7dJQ;YkQ;e!E7oRZgGG($dBGtff*+)c{Tx4q`r^LC@0mLT&_qJa=JI zXdqvodiR*Z?+yi(9l?RNsQgcD09*-$>YpNDsJ%6fQ<8Y-NPKPL z5cRr-WYbhXcs%Qw7*56&F9u`~3mu0M?e25dgMTeSGuf5SHJO+MXEN5x=&zInIY6YT&X z6gr!d!*iYzD$;gPqo@;LfU6;Uu{(Ho4$nDki1sBjSz4eDw@X_ok~|4+o2SG#9YVGM z_tx6pkkulbsI>U|Gp;jfXKrYY6T5ff^^f~E!Bvz8@cvm*IfI=O_$zETaaj%3HPb^wp`M)2hm;0r2n#j zF3>Jrn!dN8a)a?&bSnSqaP2EUjgmqQ@?qYi`cN|QK@~bLRW0QittJS%%Bq++E{WaS zXbnvbV0+yaIX%Kj$C^o19DN}_pCeU}l8J%?N@t~k0000100mesH842<00Or#f&#NJ zf&rVL0|Eg80tiHqMQi<$JFGAb1_>&LNQU)CZ2QEFY$X2BvwV*a9@2t9h*raZ`aPjJ?tuUV=G z^f0ZX>t#Hs{iynJ=6{mEL^_qVw=9Hi{lN01;OqB5U(l3r=aR8+bwk$A>mFrXJtVP> z9sd7h!LzhzV4k0%-l8taG(8|l1IO%8;UW-L>-$;u>m9I5eB+*pvz|Gge+NuB;}6M% zgaP5`5x!ZJ+bFvCZ;hO@(#2d*|Ef-;mSilBKUd_*;znM5EdkxNtOB_uH`a|Xe_gaG z4KO>na;&cP;Eud;nPg}nYxDzbvHgEH_7sa(Q*9hl-jPcq(ae>)Y1!792Lb~D00A%! z1_>&LNQU+ukI3SBkgNRstfyPFRL^VNjjicG6&TNCu$Y(D4 zBfA74*SJAD=D9kHD)+3%VW+D+{piDm_{?WCWdgRbS;u&*-+bKku`JvCyFf|5S_zk# z=X|_+(QvodB-T{g8BqdXQ^~7a%Q literal 0 HcmV?d00001 diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java index 1aa3150e..e81e3449 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java @@ -45,6 +45,7 @@ public EzySimpleSocketSetting() { public Map toMap() { Map map = super.toMap(); map.put("sslActive", sslActive); + map.put("sslType", sslType); map.put("tcpNoDelay", tcpNoDelay); map.put("maxRequestSize", maxRequestSize); map.put("writerThreadPoolSize", writerThreadPoolSize); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java index 6e7e3fbf..9d9fface 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java @@ -7,14 +7,10 @@ import com.tvd12.ezyfox.util.EzyLoggable; import lombok.Setter; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; +import javax.net.ssl.*; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.SecureRandom; @Setter public class EzySimpleSslContextFactory @@ -24,8 +20,6 @@ public class EzySimpleSslContextFactory protected static final String SUNX509 = "SunX509"; protected static final String PROTOCOL = "TLS"; protected static final String JKS_KEYSTORE = "JKS"; - protected SecureRandom secureRandom; - protected TrustManager[] trustManagers; @Override public SSLContext newSslContext(EzySslConfig config) throws Exception { @@ -43,10 +37,15 @@ protected SSLContext tryNewSslContext(EzySslConfig config) throws Exception { KeyManagerFactory keyManagerFactory = newKeyManagerFactory(config); initKeyManagerFactory(keyManagerFactory, keyStore, certificatePassword); + // Set up trust manager factory to use our key store + TrustManagerFactory trustManagerFactory = newTrustManagerFactory(config); + initTrustManagerFactory(trustManagerFactory, keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + // Initialize the SSLContext to work with our key managers. SSLContext context = SSLContext.getInstance(getProtocol()); KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); - context.init(keyManagers, null, null); + context.init(keyManagers, trustManagers, null); return context; } @@ -54,18 +53,34 @@ protected void initKeyManagerFactory( KeyManagerFactory factory, KeyStore keyStore, char[] password - ) - throws Exception { + ) throws Exception { factory.init(keyStore, password); } - protected KeyManagerFactory newKeyManagerFactory(EzySslConfig config) - throws Exception { + protected KeyManagerFactory newKeyManagerFactory( + EzySslConfig config + ) throws Exception { return KeyManagerFactory.getInstance(getAlgorithm(config)); } - protected void loadKeyStore(KeyStore keyStore, InputStream stream, char[] password) - throws Exception { + protected void initTrustManagerFactory( + TrustManagerFactory factory, + KeyStore keyStore + ) throws Exception { + factory.init(keyStore); + } + + protected TrustManagerFactory newTrustManagerFactory( + EzySslConfig config + ) throws Exception { + return TrustManagerFactory.getInstance(getAlgorithm(config)); + } + + protected void loadKeyStore( + KeyStore keyStore, + InputStream stream, + char[] password + ) throws Exception { try { keyStore.load(stream, password); } finally { @@ -91,7 +106,6 @@ protected InputStream loadKeyStoreStream(String file) { return newInputStreamLoader().load(file); } - @SuppressWarnings("unused") protected KeyStore newKeyStore(EzySslConfig config) throws KeyStoreException { return KeyStore.getInstance(getKeyStoreType()); } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactoryBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactoryBuilder.java index b07190ce..d5f00a6f 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactoryBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactoryBuilder.java @@ -1,24 +1,10 @@ package com.tvd12.ezyfoxserver.ssl; -import javax.net.ssl.TrustManager; -import java.security.SecureRandom; - public class EzySimpleSslContextFactoryBuilder implements EzySslContextFactoryBuilder { @Override public EzySslContextFactory build() { - EzySimpleSslContextFactory factory = new EzySimpleSslContextFactory(); - factory.setSecureRandom(getSecureRandom()); - factory.setTrustManagers(getTrustManagers()); - return factory; - } - - protected TrustManager[] getTrustManagers() { - return new EzySslTrustManagerFactory().engineGetTrustManagers(); - } - - protected SecureRandom getSecureRandom() { - return new SecureRandom(); + return new EzySimpleSslContextFactory(); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index e868fcfd..76939e36 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -10,8 +10,6 @@ import com.tvd12.ezyfoxserver.socket.EzySocketWritingLoopHandler; import javax.net.ssl.SSLContext; - -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; @@ -68,34 +66,14 @@ private void newAndConfigServerSocketChannel() throws Exception { } private void getBindAndConfigServerSocket() throws Exception { - this.serverSocket = createServerSocket(); + this.serverSocket = serverSocketChannel.socket(); + this.serverSocket.setReuseAddress(true); + this.serverSocket.bind(new InetSocketAddress(getSocketAddress(), getSocketPort())); this.serverSocketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT); } - private ServerSocket createServerSocket() throws Exception { - ServerSocket server; - EzySocketSetting socketSetting = getSocketSetting(); - boolean sslActive = socketSetting.isSslActive(); - SslType sslType = socketSetting.getSslType(); - if (sslActive && sslType == SslType.L4) { - server = sslContext - .getServerSocketFactory() - .createServerSocket( - getSocketPort(), - 0, - InetAddress.getByName(getSocketAddress()) - ); - server.setReuseAddress(true); - } else { - server = serverSocketChannel.socket(); - server.setReuseAddress(true); - server.bind(new InetSocketAddress(getSocketAddress(), getSocketPort())); - } - return server; - } - private void startSocketHandlers() throws Exception { - EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); + EzyNioSocketAcceptor socketAcceptor = newSocketAcceptor(); writingLoopHandler = newWritingLoopHandler(); readingLoopHandler = newReadingLoopHandler(socketAcceptor); socketAcceptanceLoopHandler = newSocketAcceptanceLoopHandler(socketAcceptor); @@ -104,6 +82,14 @@ private void startSocketHandlers() throws Exception { writingLoopHandler.start(); } + private EzyNioSocketAcceptor newSocketAcceptor() { + EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); + if (isEnableL4Ssl()) { + acceptor.setSslContext(sslContext); + } + return acceptor; + } + private EzySocketEventLoopHandler newWritingLoopHandler() { EzySocketWritingLoopHandler loopHandler = new EzySocketWritingLoopHandler(); loopHandler.setThreadPoolSize(getSocketWriterPoolSize()); @@ -117,7 +103,8 @@ private EzySocketEventLoopHandler newWritingLoopHandler() { } private EzySocketEventLoopHandler newReadingLoopHandler( - EzyNioAcceptableConnectionsHandler acceptableConnectionsHandler) { + EzyNioAcceptableConnectionsHandler acceptableConnectionsHandler + ) { EzySocketEventLoopOneHandler loopHandler = new EzyNioSocketReadingLoopHandler(); loopHandler.setThreadPoolSize(getSocketReaderPoolSize()); EzyNioSocketReader eventHandler = new EzyNioSocketReader(); @@ -149,6 +136,11 @@ private ServerSocketChannel newServerSocketChannel() throws Exception { return ServerSocketChannel.open(); } + private boolean isEnableL4Ssl() { + EzySocketSetting setting = getSocketSetting(); + return setting.isSslActive() && setting.getSslType() == SslType.L4; + } + private int getSocketReaderPoolSize() { return EzyNioThreadPoolSizes.SOCKET_READER; } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyHandlerGroupBuilderFactoryImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyHandlerGroupBuilderFactoryImpl.java index 79c6e8e1..e2ef2688 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyHandlerGroupBuilderFactoryImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyHandlerGroupBuilderFactoryImpl.java @@ -54,7 +54,10 @@ public static Builder builder() { } @Override - public EzyAbstractHandlerGroup.Builder newBuilder(EzyChannel channel, EzyConnectionType type) { + public EzyAbstractHandlerGroup.Builder newBuilder( + EzyChannel channel, + EzyConnectionType type + ) { EzyAbstractHandlerGroup.Builder builder = (type == EzyConnectionType.SOCKET) ? newBuilderBySocketType() : newBuilderByWebSocketType(); @@ -69,12 +72,11 @@ public EzyAbstractHandlerGroup.Builder newBuilder(EzyChannel channel, EzyConnect } private EzyAbstractHandlerGroup.Builder newBuilderBySocketType() { - EzyAbstractHandlerGroup.Builder builder = EzySimpleNioHandlerGroup.builder(); - builder.sessionCount(socketSessionCount); - builder.sessionStats(getSocketSessionStats()); - builder.networkStats(getSocketNetworkStats()); - builder.sessionTicketsQueue(socketSessionTicketsQueue); - return builder; + return EzySimpleNioHandlerGroup.builder() + .sessionCount(socketSessionCount) + .sessionStats(getSocketSessionStats()) + .networkStats(getSocketNetworkStats()) + .sessionTicketsQueue(socketSessionTicketsQueue); } private EzyAbstractHandlerGroup.Builder newBuilderByWebSocketType() { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java index eefb3834..d23c6fa3 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java @@ -169,8 +169,7 @@ private EzySocketRequest newSocketRequest(Object data) { protected final void executeSendingPacket(EzyPacket packet, Object writeBuffer) { try { - EzyChannel channel = session.getChannel(); - if (canWriteBytes(channel)) { + if (session.isActivated() && channel.isConnected()) { EzyConstant transportType = packet.getTransportType(); if (transportType == EzyTransportType.UDP_OR_TCP) { transportType = session.getDatagramChannelPool() != null @@ -186,7 +185,7 @@ protected final void executeSendingPacket(EzyPacket packet, Object writeBuffer) int packetSize = packet.getSize(); networkStats.addWriteErrorPackets(1); networkStats.addWriteErrorBytes(packetSize); - logger.warn( + logger.info( "can't send {} bytes to session: {}, error: {}({})", packetSize, session, @@ -238,13 +237,6 @@ protected ByteBuffer getWriteBuffer(ByteBuffer fixed, int bytesToWrite) { return bytesToWrite > fixed.capacity() ? ByteBuffer.allocate(bytesToWrite) : fixed; } - private boolean canWriteBytes(EzyChannel channel) { - if (channel == null) { - return false; - } - return channel.isConnected(); - } - protected final void executeAddReadBytes(int bytes) { statsThreadPool.execute(() -> addReadBytes(bytes)); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index 6f09a648..989842c3 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -9,6 +9,9 @@ import com.tvd12.ezyfoxserver.socket.EzySocketAbstractEventHandler; import lombok.Setter; +import javax.net.ssl.*; +import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -18,11 +21,15 @@ import java.util.Set; import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; public class EzyNioSocketAcceptor extends EzySocketAbstractEventHandler implements EzyHandlerGroupManagerAware, EzyNioAcceptableConnectionsHandler { + @Setter + protected SSLContext sslContext; @Setter protected boolean tcpNoDelay; @Setter @@ -49,12 +56,11 @@ public void handleEvent() { } public void handleAcceptableConnections() { - if (acceptableConnections.isEmpty()) { - return; - } - //noinspection SynchronizeOnNonFinalField - synchronized (acceptableConnections) { - doHandleAcceptableConnections(); + if (acceptableConnections.size() > 0) { + //noinspection SynchronizeOnNonFinalField + synchronized (acceptableConnections) { + doHandleAcceptableConnections(); + } } } @@ -106,7 +112,12 @@ private void acceptConnection(SocketChannel clientChannel) { private void doAcceptConnection(SocketChannel clientChannel) throws Exception { clientChannel.configureBlocking(false); clientChannel.socket().setTcpNoDelay(tcpNoDelay); - + try { + handleSslHandshake(clientChannel); + } catch (Exception e) { + clientChannel.close(); + throw e; + } EzyChannel channel = new EzyNioSocketChannel(clientChannel); EzyNioHandlerGroup handlerGroup = handlerGroupManager @@ -116,4 +127,200 @@ private void doAcceptConnection(SocketChannel clientChannel) throws Exception { SelectionKey selectionKey = clientChannel.register(readSelector, SelectionKey.OP_READ); session.setProperty(EzyNioSession.SELECTION_KEY, selectionKey); } + + protected EzyNioHandlerGroup newHandlerGroup(EzyChannel channel) { + return handlerGroupManager + .newHandlerGroup(channel, EzyConnectionType.SOCKET); + } + + @SuppressWarnings("MethodLength") + protected void handleSslHandshake(SocketChannel socketChannel) throws IOException { + SSLEngine engine = sslContext.createSSLEngine(); + engine.setUseClientMode(false); + engine.beginHandshake(); + + SSLSession session = engine.getSession(); + int appBufferSize = session.getApplicationBufferSize(); + int packetBufferSize = session.getPacketBufferSize(); + ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize); + ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); + ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize); + ByteBuffer myNetData = ByteBuffer.allocate(packetBufferSize); + + SSLEngineResult result; + SSLException sslException = null; + SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { + switch (handshakeStatus) { + case NEED_UNWRAP: + if (socketChannel.read(peerNetData) < 0) { + if (engine.isInboundDone() && engine.isOutboundDone()) { + throw new SSLException( + "status is NEED_UNWRAP " + + "while inbound and outbound done" + ); + } + try { + engine.closeInbound(); + } catch (SSLException e) { + sslException = e; + logger.info( + "This engine was forced to close inbound, " + + "without having received the proper SSL/TLS close " + + "notification message from the peer, due to end of stream." + ); + } + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + peerNetData.flip(); + try { + result = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException e) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + sslException = e; + logger.error( + "A problem was encountered while processing the data " + + "that caused the SSLEngine to abort. " + + "Will try to properly close connection..." + ); + break; + } + switch (result.getStatus()) { + case OK: + break; + case BUFFER_OVERFLOW: + peerAppData = enlargeApplicationBuffer(engine, peerAppData); + break; + case BUFFER_UNDERFLOW: + peerNetData = handleBufferUnderflow(engine, peerNetData); + break; + case CLOSED: + if (engine.isOutboundDone()) { + throw new SSLException("status CLOSED while outbound done"); + } else { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + default: + throw new IllegalStateException( + "Invalid SSL status: " + result.getStatus() + ); + } + break; + case NEED_WRAP: + myNetData.clear(); + try { + result = engine.wrap(myAppData, myNetData); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException e) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + sslException = e; + logger.error( + "A problem was encountered while processing the data " + + "that caused the SSLEngine to abort. " + + "Will try to properly close connection..." + ); + break; + } + switch (result.getStatus()) { + case OK : + myNetData.flip(); + while (myNetData.hasRemaining()) { + socketChannel.write(myNetData); + } + break; + case BUFFER_OVERFLOW: + myNetData = enlargePacketBuffer(engine, myNetData); + break; + case BUFFER_UNDERFLOW: + throw new SSLException("Buffer underflow occurred after a wrap."); + case CLOSED: + try { + myNetData.flip(); + while (myNetData.hasRemaining()) { + socketChannel.write(myNetData); + } + peerNetData.clear(); + } catch (Exception e) { + logger.error( + "Failed to send server's close message " + + "due to socket channel's failure." + ); + handshakeStatus = engine.getHandshakeStatus(); + } + break; + default: + throw new SSLException( + "Invalid SSL status: " + result.getStatus() + ); + } + break; + case NEED_TASK: + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + task.run(); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + default: + throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); + } + } + if (sslException != null) { + throw sslException; + } + } + + protected ByteBuffer handleBufferUnderflow( + SSLEngine engine, + ByteBuffer buffer + ) { + if (engine.getSession().getPacketBufferSize() < buffer.limit()) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer); + buffer.flip(); + replaceBuffer.put(buffer); + return replaceBuffer; + } + } + + protected ByteBuffer enlargePacketBuffer( + SSLEngine engine, + ByteBuffer buffer + ) { + return enlargeBuffer( + buffer, + engine.getSession().getPacketBufferSize() + ); + } + + protected ByteBuffer enlargeApplicationBuffer( + SSLEngine engine, + ByteBuffer buffer + ) { + return enlargeBuffer( + buffer, + engine.getSession().getApplicationBufferSize() + ); + } + + protected ByteBuffer enlargeBuffer( + ByteBuffer buffer, + int sessionProposedCapacity + ) { + if (sessionProposedCapacity > buffer.capacity()) { + buffer = ByteBuffer.allocate(sessionProposedCapacity); + } else { + buffer = ByteBuffer.allocate(buffer.capacity() * 2); + } + return buffer; + } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java index 2bba2e32..0507047d 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java @@ -119,23 +119,23 @@ private void tcpCloseConnection(SocketChannel channel) { } } - public void udpReceive(Object socketChannel, EzyMessage message) { + public void udpReceive(Object channel, EzyMessage message) { ExecutorService executorService = - selectedExecutorService(socketChannel); - executorService.execute(() -> doUdpReceive(socketChannel, message)); + selectedExecutorService(channel); + executorService.execute(() -> doUdpReceive(channel, message)); } - private void doUdpReceive(Object socketChannel, EzyMessage message) { + private void doUdpReceive(Object channel, EzyMessage message) { try { EzyNioHandlerGroup handlerGroup = - handlerGroupManager.getHandlerGroup(socketChannel); + handlerGroupManager.getHandlerGroup(channel); if (handlerGroup != null) { handlerGroup.fireMessageReceived(message); } } catch (Throwable e) { logger.info( "I/O error at udp-message-received (channel: {}): {}({})", - socketChannel, + channel, e.getClass().getName(), e.getMessage() ); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index b683e558..d61f5f8c 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java @@ -263,7 +263,7 @@ public void executeSendingPacketCanNotWriteBytes() throws Exception { // then EzySession session = FieldUtil.getFieldValue(sut, "session"); - verify(session, times(3)).getChannel(); + verify(session, times(2)).getChannel(); } @Test From 66a3e36d347a9b4b460711a4c6ccc9ef06b20811 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 11 Jul 2023 17:45:10 +0700 Subject: [PATCH 04/46] add EzySslHandshakeHandler --- .../ezyfoxserver/socket/EzySocketWriter.java | 2 +- .../ssl/EzySslHandshakeHandler.java | 206 +++++++++++++++++ .../nio/EzySocketServerBootstrap.java | 5 +- .../nio/socket/EzyNioSocketAcceptor.java | 212 +----------------- .../handler/EzyAbstractHandlerGroupTest.java | 32 ++- 5 files changed, 238 insertions(+), 219 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java index 938a2c82..2a49da1a 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java @@ -31,7 +31,7 @@ private void doProcessSessionTicketsQueue() { } catch (InterruptedException e) { logger.info("socket-writer thread interrupted"); } catch (Throwable throwable) { - logger.warn("problems in socket-writer, thread", throwable); + logger.info("problems in socket-writer, thread", throwable); } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java new file mode 100644 index 00000000..feb77242 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -0,0 +1,206 @@ +package com.tvd12.ezyfoxserver.ssl; + +import com.tvd12.ezyfox.util.EzyLoggable; +import lombok.AllArgsConstructor; + +import javax.net.ssl.*; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + +@AllArgsConstructor +public class EzySslHandshakeHandler extends EzyLoggable { + + private final SSLContext sslContext; + + @SuppressWarnings("MethodLength") + public void handle(SocketChannel socketChannel) throws IOException { + SSLEngine engine = sslContext.createSSLEngine(); + engine.setUseClientMode(false); + engine.beginHandshake(); + + SSLSession session = engine.getSession(); + int appBufferSize = session.getApplicationBufferSize(); + int packetBufferSize = session.getPacketBufferSize(); + ByteBuffer appBuffer = ByteBuffer.allocate(appBufferSize); + ByteBuffer netBuffer = ByteBuffer.allocate(packetBufferSize); + ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); + ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize); + + SSLEngineResult result; + SSLException sslException = null; + SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { + switch (handshakeStatus) { + case NEED_UNWRAP: + if (socketChannel.read(peerNetData) < 0) { + if (engine.isInboundDone() && engine.isOutboundDone()) { + throw new SSLException( + "status is NEED_UNWRAP " + + "while inbound and outbound done" + ); + } + try { + engine.closeInbound(); + } catch (SSLException e) { + sslException = e; + logger.info( + "This engine was forced to close inbound, " + + "without having received the proper SSL/TLS close " + + "notification message from the peer, due to end of stream." + ); + } + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + peerNetData.flip(); + try { + result = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException e) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + sslException = e; + logger.error( + "A problem was encountered while processing the data " + + "that caused the SSLEngine to abort. " + + "Will try to properly close connection..." + ); + break; + } + switch (result.getStatus()) { + case OK: + break; + case BUFFER_OVERFLOW: + peerAppData = enlargeApplicationBuffer(engine, peerAppData); + break; + case BUFFER_UNDERFLOW: + peerNetData = handleBufferUnderflow(engine, peerNetData); + break; + case CLOSED: + if (engine.isOutboundDone()) { + throw new SSLException("status CLOSED while outbound done"); + } else { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + default: + throw new IllegalStateException( + "Invalid SSL status: " + result.getStatus() + ); + } + break; + case NEED_WRAP: + netBuffer.clear(); + try { + result = engine.wrap(appBuffer, netBuffer); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException e) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + sslException = e; + logger.error( + "A problem was encountered while processing the data " + + "that caused the SSLEngine to abort. " + + "Will try to properly close connection..." + ); + break; + } + switch (result.getStatus()) { + case OK : + netBuffer.flip(); + while (netBuffer.hasRemaining()) { + socketChannel.write(netBuffer); + } + break; + case BUFFER_OVERFLOW: + netBuffer = enlargePacketBuffer(engine, netBuffer); + break; + case BUFFER_UNDERFLOW: + throw new SSLException("Buffer underflow occurred after a wrap."); + case CLOSED: + try { + netBuffer.flip(); + while (netBuffer.hasRemaining()) { + socketChannel.write(netBuffer); + } + peerNetData.clear(); + } catch (Exception e) { + logger.error( + "Failed to send server's close message " + + "due to socket channel's failure." + ); + handshakeStatus = engine.getHandshakeStatus(); + } + break; + default: + throw new SSLException( + "Invalid SSL status: " + result.getStatus() + ); + } + break; + case NEED_TASK: + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + task.run(); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + default: + throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); + } + } + if (sslException != null) { + throw sslException; + } + } + + protected ByteBuffer handleBufferUnderflow( + SSLEngine engine, + ByteBuffer buffer + ) { + if (engine.getSession().getPacketBufferSize() < buffer.limit()) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer); + buffer.flip(); + replaceBuffer.put(buffer); + return replaceBuffer; + } + } + + protected ByteBuffer enlargePacketBuffer( + SSLEngine engine, + ByteBuffer buffer + ) { + return enlargeBuffer( + buffer, + engine.getSession().getPacketBufferSize() + ); + } + + protected ByteBuffer enlargeApplicationBuffer( + SSLEngine engine, + ByteBuffer buffer + ) { + return enlargeBuffer( + buffer, + engine.getSession().getApplicationBufferSize() + ); + } + + protected ByteBuffer enlargeBuffer( + ByteBuffer buffer, + int sessionProposedCapacity + ) { + return sessionProposedCapacity > buffer.capacity() + ? ByteBuffer.allocate(sessionProposedCapacity) + : ByteBuffer.allocate(buffer.capacity() * 2); + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 76939e36..d626b212 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -8,6 +8,7 @@ import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopOneHandler; import com.tvd12.ezyfoxserver.socket.EzySocketWriter; import com.tvd12.ezyfoxserver.socket.EzySocketWritingLoopHandler; +import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import javax.net.ssl.SSLContext; import java.net.InetSocketAddress; @@ -85,7 +86,9 @@ private void startSocketHandlers() throws Exception { private EzyNioSocketAcceptor newSocketAcceptor() { EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); if (isEnableL4Ssl()) { - acceptor.setSslContext(sslContext); + acceptor.setSslHandshakeHandler( + new EzySslHandshakeHandler(sslContext) + ); } return acceptor; } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index 989842c3..12eb20fc 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -7,11 +7,9 @@ import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManagerAware; import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.ezyfoxserver.socket.EzySocketAbstractEventHandler; +import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import lombok.Setter; -import javax.net.ssl.*; -import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -21,15 +19,11 @@ import java.util.Set; import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; -import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; -import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; public class EzyNioSocketAcceptor extends EzySocketAbstractEventHandler implements EzyHandlerGroupManagerAware, EzyNioAcceptableConnectionsHandler { - @Setter - protected SSLContext sslContext; @Setter protected boolean tcpNoDelay; @Setter @@ -40,6 +34,8 @@ public class EzyNioSocketAcceptor protected List acceptableConnections; @Setter protected EzyHandlerGroupManager handlerGroupManager; + @Setter + protected EzySslHandshakeHandler sslHandshakeHandler; @Override public void destroy() { @@ -105,7 +101,7 @@ private void acceptConnection(SocketChannel clientChannel) { try { doAcceptConnection(clientChannel); } catch (Exception e) { - logger.error("can't accept connection: {}", clientChannel, e); + logger.info("can't accept connection: {}", clientChannel, e); } } @@ -113,7 +109,9 @@ private void doAcceptConnection(SocketChannel clientChannel) throws Exception { clientChannel.configureBlocking(false); clientChannel.socket().setTcpNoDelay(tcpNoDelay); try { - handleSslHandshake(clientChannel); + if (sslHandshakeHandler != null) { + sslHandshakeHandler.handle(clientChannel); + } } catch (Exception e) { clientChannel.close(); throw e; @@ -127,200 +125,4 @@ private void doAcceptConnection(SocketChannel clientChannel) throws Exception { SelectionKey selectionKey = clientChannel.register(readSelector, SelectionKey.OP_READ); session.setProperty(EzyNioSession.SELECTION_KEY, selectionKey); } - - protected EzyNioHandlerGroup newHandlerGroup(EzyChannel channel) { - return handlerGroupManager - .newHandlerGroup(channel, EzyConnectionType.SOCKET); - } - - @SuppressWarnings("MethodLength") - protected void handleSslHandshake(SocketChannel socketChannel) throws IOException { - SSLEngine engine = sslContext.createSSLEngine(); - engine.setUseClientMode(false); - engine.beginHandshake(); - - SSLSession session = engine.getSession(); - int appBufferSize = session.getApplicationBufferSize(); - int packetBufferSize = session.getPacketBufferSize(); - ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize); - ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); - ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize); - ByteBuffer myNetData = ByteBuffer.allocate(packetBufferSize); - - SSLEngineResult result; - SSLException sslException = null; - SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); - while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { - switch (handshakeStatus) { - case NEED_UNWRAP: - if (socketChannel.read(peerNetData) < 0) { - if (engine.isInboundDone() && engine.isOutboundDone()) { - throw new SSLException( - "status is NEED_UNWRAP " + - "while inbound and outbound done" - ); - } - try { - engine.closeInbound(); - } catch (SSLException e) { - sslException = e; - logger.info( - "This engine was forced to close inbound, " + - "without having received the proper SSL/TLS close " + - "notification message from the peer, due to end of stream." - ); - } - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - break; - } - peerNetData.flip(); - try { - result = engine.unwrap(peerNetData, peerAppData); - peerNetData.compact(); - handshakeStatus = result.getHandshakeStatus(); - } catch (SSLException e) { - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - sslException = e; - logger.error( - "A problem was encountered while processing the data " + - "that caused the SSLEngine to abort. " + - "Will try to properly close connection..." - ); - break; - } - switch (result.getStatus()) { - case OK: - break; - case BUFFER_OVERFLOW: - peerAppData = enlargeApplicationBuffer(engine, peerAppData); - break; - case BUFFER_UNDERFLOW: - peerNetData = handleBufferUnderflow(engine, peerNetData); - break; - case CLOSED: - if (engine.isOutboundDone()) { - throw new SSLException("status CLOSED while outbound done"); - } else { - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - break; - } - default: - throw new IllegalStateException( - "Invalid SSL status: " + result.getStatus() - ); - } - break; - case NEED_WRAP: - myNetData.clear(); - try { - result = engine.wrap(myAppData, myNetData); - handshakeStatus = result.getHandshakeStatus(); - } catch (SSLException e) { - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - sslException = e; - logger.error( - "A problem was encountered while processing the data " + - "that caused the SSLEngine to abort. " + - "Will try to properly close connection..." - ); - break; - } - switch (result.getStatus()) { - case OK : - myNetData.flip(); - while (myNetData.hasRemaining()) { - socketChannel.write(myNetData); - } - break; - case BUFFER_OVERFLOW: - myNetData = enlargePacketBuffer(engine, myNetData); - break; - case BUFFER_UNDERFLOW: - throw new SSLException("Buffer underflow occurred after a wrap."); - case CLOSED: - try { - myNetData.flip(); - while (myNetData.hasRemaining()) { - socketChannel.write(myNetData); - } - peerNetData.clear(); - } catch (Exception e) { - logger.error( - "Failed to send server's close message " + - "due to socket channel's failure." - ); - handshakeStatus = engine.getHandshakeStatus(); - } - break; - default: - throw new SSLException( - "Invalid SSL status: " + result.getStatus() - ); - } - break; - case NEED_TASK: - Runnable task; - while ((task = engine.getDelegatedTask()) != null) { - task.run(); - } - handshakeStatus = engine.getHandshakeStatus(); - break; - default: - throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); - } - } - if (sslException != null) { - throw sslException; - } - } - - protected ByteBuffer handleBufferUnderflow( - SSLEngine engine, - ByteBuffer buffer - ) { - if (engine.getSession().getPacketBufferSize() < buffer.limit()) { - return buffer; - } else { - ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer); - buffer.flip(); - replaceBuffer.put(buffer); - return replaceBuffer; - } - } - - protected ByteBuffer enlargePacketBuffer( - SSLEngine engine, - ByteBuffer buffer - ) { - return enlargeBuffer( - buffer, - engine.getSession().getPacketBufferSize() - ); - } - - protected ByteBuffer enlargeApplicationBuffer( - SSLEngine engine, - ByteBuffer buffer - ) { - return enlargeBuffer( - buffer, - engine.getSession().getApplicationBufferSize() - ); - } - - protected ByteBuffer enlargeBuffer( - ByteBuffer buffer, - int sessionProposedCapacity - ) { - if (sessionProposedCapacity > buffer.capacity()) { - buffer = ByteBuffer.allocate(sessionProposedCapacity); - } else { - buffer = ByteBuffer.allocate(buffer.capacity() * 2); - } - return buffer; - } } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index d61f5f8c..35d2ee8b 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java @@ -89,16 +89,20 @@ public void test() throws Exception { .build(); EzyChannel channelX = mock(EzyChannel.class); - MethodInvoker.create() - .object(group) - .method("canWriteBytes") - .param(EzyChannel.class, null) - .invoke(); - MethodInvoker.create() - .object(group) - .method("canWriteBytes") - .param(EzyChannel.class, channelX) - .invoke(); + try { + MethodInvoker.create() + .object(group) + .method("canWriteBytes") + .param(EzyChannel.class, null) + .invoke(); + MethodInvoker.create() + .object(group) + .method("canWriteBytes") + .param(EzyChannel.class, channelX) + .invoke(); + } catch (Throwable e) { + e.printStackTrace(); + } sessionTicketsRequestQueues = mock(EzySessionTicketsRequestQueues.class); when(sessionTicketsRequestQueues.addRequest(any())).thenReturn(false); @@ -375,6 +379,7 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualUdp() throws EzySimpleSession session = mock(EzySimpleSession.class); when(session.getChannel()).thenReturn(channel); + when(session.isActivated()).thenReturn(true); EzyDatagramChannelPool datagramChannelPool = mock(EzyDatagramChannelPool.class); @@ -418,7 +423,8 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualUdp() throws Asserts.assertNotNull(group.getSession()); // then - verify(session, times(3)).getChannel(); + verify(session, times(1)).isActivated(); + verify(session, times(2)).getChannel(); verify(session, times(2)).getDatagramChannelPool(); verify(session, times(1)).getUdpClientAddress(); } @@ -447,6 +453,7 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualTcp() throws EzySimpleSession session = mock(EzySimpleSession.class); when(session.getChannel()).thenReturn(channel); + when(session.isActivated()).thenReturn(true); InetSocketAddress udpAddress = new InetSocketAddress("127.0.0.1", 12348); when(session.getUdpClientAddress()).thenReturn(udpAddress); @@ -486,7 +493,8 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualTcp() throws Asserts.assertNotNull(group.getSession()); // then - verify(session, times(3)).getChannel(); + verify(session, times(1)).isActivated(); + verify(session, times(2)).getChannel(); verify(session, times(1)).getDatagramChannelPool(); verify(session, times(0)).getUdpClientAddress(); } From be44160d86b498d67d5d1c7654908eb874cdf409 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 11 Jul 2023 17:49:56 +0700 Subject: [PATCH 05/46] update keystore --- .../settings/ssl/ssl-keystore.txt | Bin 2055 -> 2046 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ezyfox-server-core/settings/ssl/ssl-keystore.txt b/ezyfox-server-core/settings/ssl/ssl-keystore.txt index 78ab0217e62d5cfc331a0531d656791ee91021ba..c75c720d60948f449aff647d8d0a5d4f574efbb8 100644 GIT binary patch delta 1980 zcmV;t2SfOW5dII4B!7i0H2?qw0x*IE{xA*(3M&Qy1OX}n5di@O00e>r>hQM36=cPE zgP_H1y+1JXQr=d8wZ|lP#hlLMF|T_!%mT!YyynSr(TLx$>^XV;A~-xa%P+t$OOF$6 zPBwrSv`cgS;C*WH)AoMx;^B?}$fd9d&FUDJ08Vo;XH>-PC4Xk$z+Ir)^1OHj1@RoT ztw&$EZM0ZcB4in+twjcd~qDwo$8kjCUyjo5UyV-dJ zL_BNBu@M8M>S$={0fk<&b@T1S_BTLWZD-tFf?nk{KYt5!Ts}8UhjR@^ce{)usbJOx zTV&eZ4Z=@l@~XAS#EF?S>G7~dps6FMLqt7ph!bt6j`Rpjiy%dF4j_!EJG0WwT{qba zc8!nY$H2B?*1%%Q0#OhYO~RXrOrmDkTjDo$E52?oX-H?iH>yC}*w^o#NdN+^is$tW z!Yu&t7k>@#b!6cW{)!Aeqo((JZ~ljk=rL8m47c5~_&<0jk^!j=GcUX`WzwaQb&6cNMk>h7ODL2ilJal2i!nADCGPlXy6+_BbK;eQW3Rt{kkR?|V~%9MngTE5N2abvSM zudH(G2vvFyt3g<~_GbcZf~Wpsci8f|73K9Y^tVh=<#90$3}a0Rn6b z-hgYP{ZA1rMW-~US=dM*xWkw6`DZ;DN6BBUrQB@VI|>_xc?Vm84^U{1@89OL6u#eq zwtufkrOj+DOsj8)6-sC=yxlZBHH$g*C2hE2AaR7XvPr(2zpYyHc2VN9)Cg(~U}S0~ zhi2-(!`MhB;@H)41)hVa3(1vT>toSAm(M$Ra7^5@O8dDBqDL;@x;d#PPN7*4jIEDB z$q9P?qmY@2NtwxJ;7G|36{11*yuo7_n|}iHM?%;{C8KVY*2c_Gf1IJ*nht4r$F3Oe zuVgr{3~GG%M_}9=vgov9pW@QKvnfic5Fa&HS4zrIcI zjKj_ar_zd~7mUv7qr4Bp!-30|B^%L4Bh3DEgSo^+A+fuCwtqZ&%}S3G0|8b>D1Wuu zpacnF*vhS#{4OGKO7CyoX()3@)%sh&drkW#|NG~*;vM5GiJSKPVHU|1DMunMPfNAu zVN;>&qyo4}-AzsPPi)49&MOtANPY?nLIfZb!$~%z_L4WG{L~p$?NMqXOK#w6G$`6_ zoM4XoFH0~$@i=xwlz^a1p855IPJgxd;XpwsDpo0F2f-J!gqNBo^xd%v81tLW}ouHh3AQNuBKUim9K%#Q8s z-4R#`aqpw#mx^0OV5{ag7-w;snzr9gqJZAa6V#??LFX~Nx9nT>qP8@Akbl%3n|UUB zRuzMWw~{EPtj4R~L;wH)0RRP9E;TSY00086FoFWAFoFS+paTK{0s;s{j7udq2bsGt z4F(A+hDe6@4FLfQ1pqJ-F%U2g1_Mv8aLn2H7lN&i zTLs2P8`YELr!(;$9<=$2`tf>4;Nm$Cx_&jrBy7_<#yaA@{a8GgcyHFcDSj}ZFBAc4 z(*7GQH}2hYGLNm7pCNO8ME~&6ov{alfdP)*xVg)8U+CK=bZx*>&3{2n=^y-J zNW|9l6Fw_TW*q+}E0RDG?plWDJj=Jrd14OT4tn{M#sni^xGcKRVpi1yfP@i_2=)k) z)9v%kF)vDOYSKjssA+Dm1Ybzy`KE}I7|ctP>JVwD3mW?N7H6!X?Hu$KMDVi-Jmm@x zS&n1S%ByK@gS_6M7L3VlmVX5I0s{d60Wb{)2`Yw2hW8Bt0Sg5H1A+ko0B>>k;b(#w zi3vB9Ii~=FlULh)Hy?vp#34~`sv9m9ieLh~SVwN+#0s{z=23{YNV2@lyYTTzqzCx- z>ubOjiOZ!PgNwVpriuJ4w_jgsYR;~9H`!QxuK~`jD6W|?V7bxgZGUXy1ukuf&Mom= zj9sOUiknDI*0gvY75S{>BsHOsh9CdCiRhFI1xbP*WFIu`cxdCW}&K+j4Mij%%1vk&>y? zEuQ#~@9q`!tI;BIT`^x$;CG>wRT#%-g4lh|10f|K=b5jhf;RZ@R`>3atTbq7HH7c~ O2i8ok+bK#Z4+|q delta 1966 zcmV;f2T}O`4~GztB!6XC?f?J<0x*IE{xA*(3M&Qy1OX}n5di@O00e>r>c(*PLS*NY z&Ly=zVRa%{xl>~W-S6}wVU><=9ZZ+?9HP1Avt!`nF`Li%u!qA4_-Yg2XrrOm``426 zQaFHm5hU;qngDQ)?K0%&xq#?KFqQ63sGbgzEEe^bRAJ{T@vchZP)MW5pZ|Fr6_>q?Ii%PkYBh5 zYDL}1F=>|XoMyF|_Z}&O+=+(2Hr&%hP(bV^`AeOafdy?GTdFqX@|qTE)w+HH2TKXC zco>HEgq|@%QGoZXnhrC^;>wzxo4<(|Qt;GA(Dc{~kAGz~7(t%3nj%+s{@(d%hqx*? zM$(-WD-v$7w3f9Kc;I6l*hpgsNtN?g+zvd9#ylzMMyMN?hcwrtzhtH|kPl^OGs+L0 z_YrEfq;5-sy-KtDm%gt`k~V?#PEB?ueKT{Q1emK2x?5u`{5W&4o?ap*{8xXMq+u0{ zT>!qt)_)avDoyCgBANK!EP?=c6s`BUViwYP{ZdEE=I{y-6PW-wD(*#m8JvdJF+?6b z$Ui=55?Bj3YD@%rgJO>U#B=yqFNbOPh}x;nBNlw0{u@zx!6zo&@Qx<+an0SOAc8VI zmGjihESfo@_n{6hr<9LhQeLEdl8m{bi!J=R3x8$eZ27v}!yWE4@WOs)d&!{wZo=33 z&oovNh~<)xdr+j!A*%RgG3hX_xD#I|dtZ|9OK`hWnXDAxWKi|qnYBTEa;_#YNwO;R zVsf`6QOCZ2)y!y$Yu@!tI8O~C#RvCWlln>FYi@%)NJ{>@>7dJQ;YkQ;e!E7oRZgGG z(tpy$`K+Z`_#QfMGwpL+M0!tV|Rl^wx>wy6A1sZ2Rv zAxg%1=*KL)TmW1Nh3cOoV5q${jZ>0%=tz8R;t=(^hGf%JKX^R*GALdf#KLWgiD-W} zk4JG$$hP@0tC|4S8|2cOP=*tEDj8ry_kZ4E3VMZ%K7Y9`V}kVdGsP+UDjysb`fr9r zVPFUCEj_d2#0@mnban?&z&r!FVV152~Qlu{mPu^T6s<>u(Rv9J#SG^sBBv zjRFR2KDt}9_}PEMHttP6-A_lwV1K@Ox7-u$03Z}Po07wGo)Rk3c2J|J6JUU=A%AlRg?$i@doA)jsWZ8QC<>~WYcy50)d?+{ z4J8t=Qc=z@)+^MGvqRO}eS@cvm*IfI=O_$zETaaj%3HPb^wp`M)2hm;0r2n#jF3>Jrn!dN8a)a?&bSnSqaP2EU zjgmqQ@?qYi`cN|QK@~bLRW0QittJS%%Bq++E{WaSXbnvbV0+yaIX%Kj$A6khRUCaG zKc6F2kdldl14?J5fdBvi0RRP9E;TSY0008FFoFWJFoFS_paTK{0s;s`kVR|#kvps~ z4F(A+hDe6@4FLfQ1pqJ`F&Quy1_Mk4f7j~nMa@N z*>xOIYF*K0!6HRs{-T};J$l-vJji%YaMqx&S*i#0Fs-BOWjv_;sQPi{f0DmMI+eAz zEQD_T!1AQv>-Ruk(3EiJlCf`fL)Oph9%WoTB(aSh{{Ljbv$SYno}Z%LqAtlaJs?Pb z1IO%8;UW-L>-$;u>m9I5eB+*pvz|Gge+NuB;}6M%gaP5`5x!ZJ+bFvCZ;hO@(#2d* z|Ef-;mSilBKUd_*;znM5EdkxNtOB_uH`a|Xe_gaG4KO>na;&cP;Eud;nPg}nYxDzb zvHgEH_7sa(Q*9hl-jPcq(ae>)Y1!5SnFo{61{r^xJ}Ws^$T%R2N`r`4eSyYCj6^j- za*d8G-U#|v02A>tKWRw z^sy}4{JTI&zgh{Gndf}GdeLyV*Cf_d+T~fMDol_p(BDC+bzGv|&bppYa-2U*lXZOW z!(V^un=M%DH;Biw==oBA7R?ra1_wq5ij0iYStdaxD#_0+JL5yCFp@Wc1!U!zx=%WC$cPT=o}XIlLVy6#PX^uYDmCQ!5<-AIqOs~`EB~P AUH||9 From 65818bbd3d3986e64939407f29755f7cdbd29be3 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 11 Jul 2023 17:55:30 +0700 Subject: [PATCH 06/46] update --- .../tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java | 7 +++++-- .../tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java index 9d9fface..04fab2a0 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java @@ -1,5 +1,6 @@ package com.tvd12.ezyfoxserver.ssl; +import com.tvd12.ezyfox.io.EzyStrings; import com.tvd12.ezyfox.stream.EzyAnywayInputStreamLoader; import com.tvd12.ezyfox.stream.EzyInputStreamLoader; import com.tvd12.ezyfox.stream.EzyInputStreamReader; @@ -93,7 +94,7 @@ protected char[] getPassword(String file) throws Exception { char[] answer; try { answer = newInputStreamReader() - .readString(stream, "UTF-8") + .readString(stream, EzyStrings.UTF_8) .trim() .toCharArray(); } finally { @@ -106,7 +107,9 @@ protected InputStream loadKeyStoreStream(String file) { return newInputStreamLoader().load(file); } - protected KeyStore newKeyStore(EzySslConfig config) throws KeyStoreException { + protected KeyStore newKeyStore( + @SuppressWarnings("unused") EzySslConfig config + ) throws KeyStoreException { return KeyStore.getInstance(getKeyStoreType()); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index d626b212..b9094928 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -26,9 +26,9 @@ public class EzySocketServerBootstrap extends EzyAbstractSocketServerBootstrap { private Selector acceptSelector; private ServerSocket serverSocket; private ServerSocketChannel serverSocketChannel; - private final SSLContext sslContext; private EzySocketEventLoopHandler readingLoopHandler; private EzySocketEventLoopHandler socketAcceptanceLoopHandler; + private final SSLContext sslContext; public EzySocketServerBootstrap(Builder builder) { super(builder); From fc541a2b84c90acc2594168dc573d2fe29a2ccea Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Wed, 12 Jul 2023 11:31:11 +0700 Subject: [PATCH 07/46] can ssh hanshake with android client --- ezyfox-server-boot/pom.xml | 2 +- ezyfox-server-core/pom.xml | 8 +-- .../settings/ssl/ssl-keystore.txt | Bin 2046 -> 2054 bytes .../setting/EzySimpleSocketSetting.java | 15 +++++ .../setting/EzySocketSetting.java | 6 ++ .../setting/EzySocketSettingBuilder.java | 30 +++++++++- .../ssl/EzySslHandshakeHandler.java | 24 ++++++-- .../src/main/resources/ezy-settings-1.0.0.xsd | 3 + .../src/main/resources/ezy-settings.xml | 3 + ezyfox-server-embedded/pom.xml | 6 +- ezyfox-server-nio/pom.xml | 2 +- .../nio/EzySocketServerBootstrap.java | 40 +++++++++---- .../EzyNioAcceptableConnectionsHandler.java | 2 +- .../nio/socket/EzyNioSocketAcceptor.java | 54 ++++++++++++------ .../nio/socket/EzyNioSocketReader.java | 2 +- .../socket/EzyNioSocketAcceptorTest.java | 9 +-- .../socket/EzyNioSocketReaderTest.java | 7 +-- ezyfox-server-niorunner/pom.xml | 2 +- ezyfox-server-support/pom.xml | 6 +- pom.xml | 3 +- 20 files changed, 163 insertions(+), 61 deletions(-) diff --git a/ezyfox-server-boot/pom.xml b/ezyfox-server-boot/pom.xml index 6ca86ee8..4c8be789 100644 --- a/ezyfox-server-boot/pom.xml +++ b/ezyfox-server-boot/pom.xml @@ -4,7 +4,7 @@ com.tvd12 ezyfox-server - 1.2.8 + 1.2.8.1 ezyfox-server-boot diff --git a/ezyfox-server-core/pom.xml b/ezyfox-server-core/pom.xml index 6984de29..dcec09de 100644 --- a/ezyfox-server-core/pom.xml +++ b/ezyfox-server-core/pom.xml @@ -6,7 +6,7 @@ com.tvd12 ezyfox-server - 1.2.8 + 1.2.8.1 ezyfox-server-core ezyfox-server-core @@ -16,17 +16,17 @@ com.tvd12 ezyfox-codec - ${project.version} + ${ezy.version} com.tvd12 ezyfox-mapping - ${project.version} + ${ezy.version} com.tvd12 ezyfox-tools - ${project.version} + ${ezy.version} test diff --git a/ezyfox-server-core/settings/ssl/ssl-keystore.txt b/ezyfox-server-core/settings/ssl/ssl-keystore.txt index c75c720d60948f449aff647d8d0a5d4f574efbb8..9cd77b53747c699a4353aef62a241cbc79f7fb4c 100644 GIT binary patch delta 1966 zcmV;f2T}O`4~7tsBY#5TV6^}M1pzRE1pP1$1_~<%0R#am0uccL1pows1nEjVr51RL zs;o9~ui>99AfY_lPE!X@r))y{j6#q-OxI|b zn^vgD+a<*{q>R6Jf0zR-)}UG-j6fEx1)8uV1#=Fha7^{rLilM z)2BTJA}?-sHjl#07-u(n2<1z;{~h7VqH!;zwd&h)QjAr3r@uy`)o*C$9l+WJoMWm$ zhXmtwgcYYpEq??Jh!+3NlYgL>n@W>m;7qMf6SrDGtufXf?KqZ|AC6TK{)xYnLVv4? zR647M-qv??AYNj5^OEZf?cvZPI((@0VoZ1ylBg~`oxSeR(&;E@V+?S_2qIJeZv|D{ z5mCAF_|UE5)2h)<*D`o{y-djtcQSPckWJy$RPuLS@PFhVogbb*sF+0!(VVN4ypA&+ zeD^ivmLsb{HOp*ae&8{WxyIma)q#q`Iv3_kJgaVu zzLJ+E4}br#p$0DzMoeBY=4?pA9ISNp?vufWz~B`*1Ip+O+&#MmQSGGJA+8sIi%qR)EU0PVlwSXKo`K^m&g4d%m7tPjY=7;9& zTX3E7c}y!GQG#zm(%hFT7f97T3b-ws+NcS6_Ud&5t7i06+(7}pox-50@Sd$l)hO3}R^uJD(Vxn*Dai;!ILPDA_=@=(eID2YG3c;YBVP-f$8C~MT*{7r zaLK5%KYKIbts?T?M<^v|8Q`|J^v%h8SpsUZgtm0i#dq_FSWxrDNy_UC;88@I!8DKO zbeM0T+M-Gxo1d#v{LGmN-!6dGC&kz5RDYET9_lkhJsRHTxLjL8>!s8#Z@Y4U*f1vG zgz^`>8a#W$mbAhi^LJJb2lsffxxBViH!hkV39F=NU*8%u=&31e_6K97O8NOs3!z_b zY=F1B@`A-aDnkTiiuns70(0_?< z?GM*)r(Y9JB9 z8ohSyrVKy_?ll$rQ7%e%Ehi}km+QD(?Gt%L7Wv$>L~(?7msP^WuMT~_?4hbBz5}QX z!ssCzR9@_0wI1MG*%HZHT@Cna?@AtnI3*#h>87*&abD*)XBm zzxhx)HBZF8VurS@81rvywZ*QJk#4>K-s)Df;TKs7dZAoDp>Q7;~Qh&y{WA!Wu zVA85K8V^M@IBH_|Jpcdz0RRP9E;TSY0008FFoFWJFoFS_paTK{0s;tGl=cD^|H3ga z4F(A+hDe6@4FLfQ1pqJ`F&Quy1_MX@io%{Lu(c4ep=asrI@f@YrpH)M0N?4k+T$zb?{|E`lFNJ^pJBeC<`Gp4 zjzJc_ar}UtqDsXYP9AH(b6Tq+HWLls61$RsTsDWwsoZ(nK*;mLnr1%APw7ue7&-WV z-3z+DcumoMZ}j?7R89X@oyjv|g(S8zCh;Y=Z$_V{0aaboj1ANBZLV8iKzYn$ui|zi zniUz?3bpI5Szi*Ulu4f9DpmvZ{TzOrl8V{obEr@qmT91twW4I+Se%20&xg82&TN!; zD<3^jD1D4!23-NqM_9RDMMy1Xq=B*m;UAOH1{r^3{in<__MSHP&cZVgYwGd&jF8Cg zif)F5wEd30qOw@qinX_t64=oYS!1SP(mME7)&+s7UG(MffUv@yqCsp~jm4iUj)u2u z0y8VuBElRXviyO?pdICaozV$V#eCSO;zI?@<^GMg5<;i#BsU_!E**6UqFNSv?{^w& z8J2$lX3c&67!y(j?N>l{Ty1fGgz7dSNf@We*$y5T&>7X;_hr@bZjn-GzYy2vVXw Aga7~l delta 1980 zcmV;t2SfOV5dII4BY!}JEHwZC1p+XF1pY7%1_~<%0R#am0uccL1pows1nTg%#T8`5 zd4r(EY`s4)^HSbcfVIaYcg38};9xoxyqRw8Fb3_@AVR1ib;sWw;~L-dPk4}XN(J9YeI*VQR(zx1v%$FpEpW&tIM z?!Jnc2!T?HpS}Ai=nw{4DF_~@|ET`G$0u|M78-5Q3@<mAJ+FWHh^|l}VfTBw~!5Ww@J-k{@4!hZT z1VlV*$*~awrRr#C>H&pbvUT(A#P&BpTy1CEU4mZaHGe+~bX-0+Oowv~Mt8f6BdK84 z1zTj=-3`J|W%8=E$i#`6H0klMMWCr8s6#|OZio|Yr;hXpOp72za}FSks5`UL&0ROy z3wDi<SsFvzXM56i+C>n&VS(#Jys536IRnf=*pCYn_9lj#c^Y^ zIIpa7>j>+?E$>-xD70crEHLC3Q|3LlNq2}%(x9xgTY{2q*vlPDH zfq%BINTtneEljI#hZRa_Exg?{JT;3s^(Ae%VIXmYwX#XRoWHGF^LA07%?4!^45gmL(g}M-^ z+MompVc5#8nEWmxaZ2xR-DxOuNY(mVz9w&ES*EQy=;{9zW!6)8s|E>BCf z=V4Q!>!bp>N!?9N^-pZZhR!P$rAU4X3qk}S6vIh2r1p|Gr2NzwRqat~B1>-IYcwd@ zZJc0^`!7o{K=C+sM3jJ_OP=}lgMUu88lInxHIKPt3aYw1(Yx+RIgFICa;3WQ`YdY8 zBWIQdZPh^-Awd0+`=Hb5f18t%yxpO^qDTClS$n^neyiy2S+3y|5mCc2cwYG{JIs#l z?A;Mq332bEke1DMC9-Db4 zdR7&KhPRR^rmV)R-$Vca00966SS~d%IRF3xt}ucEsxX29lAr?u0RjRDMT|=&HwT%! zFbxI?Duzgg_YDC73k3i$5-|`k4h92N1Op5QMR;LtaBO8T9v2NVGcY$XFflbXHZ?F> z7Y#ErFgGwaF*P+dH85H*7?Yp{Ie&&R-1(3^nWm=iYo30RyoZ(B8y>v8aLn2H7lN&i zTLs2P8`YELr!(;$9<=$2`tf>4;Nm$Cx_&jrBy7_<#yaA@{a8GgcyHFcDSj}ZFBAc4 z(*7GQH}2hYGLNm7pCNO8ME~&6ov{alfdP)*xVg)8U+CK=bZx*>&3{2n=^y-J zNW|9l6Fw_TW*q+}E0RDG?plWDJj=Jrd14OT4tn{M#sni^xGcKRVpi1yfP@i_2=)k) z)9v%kF)vDOYSKjssA+Dm1Ybzy`KE}I7|ctP>JVwD3mW?N7H6!X?Hu$KMDVi-Jmm@x zS&n1S%ByK@gS_6M7L3VlmVX5I0s{d60Wb{)2`Yw2hW8Bt0Sg5H1A+ko0B>>k;b(#w zi3vB9Ii~=FlULh)Hy?vp#34~`sv9m9ieLh~SVwN+#0s{z=23{YNV2@lyYTTzqzCx- z>ubOjiOZ!PgNwVpriuJ4w_jgsYR;~9H`!QxuK~`jD6W|?V7bxgZGUXy1ukuf&Mom= zj9sOUiknDI*0gvY75S{>BsHOsh9CdCiRhFI1xbP*WFIu`cxdCW}&K+j4Mij%%1vk&>y? zEuQ#~@9q`!tI;BIT`^x$;CG>wRT#%-g4lh|10f|K=b5jhf;RZ@R`>3atTbq7HH7c~ O2i8ok+bK#Z4+ toMap() { Map map = super.toMap(); map.put("sslActive", sslActive); map.put("sslType", sslType); + map.put("sslHandshakeTimeout", sslHandshakeTimeout); map.put("tcpNoDelay", tcpNoDelay); map.put("maxRequestSize", maxRequestSize); map.put("writerThreadPoolSize", writerThreadPoolSize); + map.put("connectionAcceptorThreadPoolSize", connectionAcceptorThreadPoolSize); + map.put("sslConnectionAcceptorThreadPoolSize", sslConnectionAcceptorThreadPoolSize); return map; } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 02bd160d..24398663 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java @@ -5,9 +5,15 @@ public interface EzySocketSetting extends EzyBaseSocketSetting { SslType getSslType(); + int getSslHandshakeTimeout(); + boolean isTcpNoDelay(); int getMaxRequestSize(); + int getConnectionAcceptorThreadPoolSize(); + + int getSslConnectionAcceptorThreadPoolSize(); + int getWriterThreadPoolSize(); } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java index 8e092f0a..318752f2 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java @@ -8,14 +8,20 @@ public class EzySocketSettingBuilder extends EzySocketSettingBuilder> { protected SslType sslType; + protected int sslHandshakeTimeout; protected int maxRequestSize; protected boolean tcpNoDelay; + protected int connectionAcceptorThreadPoolSize; + protected int sslConnectionAcceptorThreadPoolSize; protected int writerThreadPoolSize; public EzySocketSettingBuilder() { this.port = 3005; this.sslType = SslType.L7; + this.sslHandshakeTimeout = 300; this.maxRequestSize = 32768; + this.connectionAcceptorThreadPoolSize = 1; + this.sslConnectionAcceptorThreadPoolSize = 8; this.writerThreadPoolSize = 8; this.codecCreator = "com.tvd12.ezyfox.codec.MsgPackCodecCreator"; } @@ -25,6 +31,11 @@ public EzySocketSettingBuilder sslType(SslType sslType) { return this; } + public EzySocketSettingBuilder sslHandshakeTimeout(int sslHandshakeTimeout) { + this.sslHandshakeTimeout = sslHandshakeTimeout; + return this; + } + public EzySocketSettingBuilder maxRequestSize(int maxRequestSize) { this.maxRequestSize = maxRequestSize; return this; @@ -35,6 +46,20 @@ public EzySocketSettingBuilder tcpNoDelay(boolean tcpNoDelay) { return this; } + public EzySocketSettingBuilder connectionAcceptorThreadPoolSize( + int connectionAcceptorThreadPoolSize + ) { + this.connectionAcceptorThreadPoolSize = connectionAcceptorThreadPoolSize; + return this; + } + + public EzySocketSettingBuilder sslConnectionAcceptorThreadPoolSize( + int sslConnectionAcceptorThreadPoolSize + ) { + this.sslConnectionAcceptorThreadPoolSize = sslConnectionAcceptorThreadPoolSize; + return this; + } + public EzySocketSettingBuilder writerThreadPoolSize(int writerThreadPoolSize) { this.writerThreadPoolSize = writerThreadPoolSize; return this; @@ -43,10 +68,13 @@ public EzySocketSettingBuilder writerThreadPoolSize(int writerThreadPoolSize) { @Override protected EzySimpleSocketSetting newSetting() { EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); - setting.setSslType(sslType); setting.setTcpNoDelay(tcpNoDelay); setting.setSslActive(sslActive); + setting.setSslType(sslType); + setting.setSslHandshakeTimeout(sslHandshakeTimeout); setting.setMaxRequestSize(maxRequestSize); + setting.setConnectionAcceptorThreadPoolSize(connectionAcceptorThreadPoolSize); + setting.setSslConnectionAcceptorThreadPoolSize(sslConnectionAcceptorThreadPoolSize); setting.setWriterThreadPoolSize(writerThreadPoolSize); setting.setCodecCreator(codecCreator); return setting; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index feb77242..04ab3cf6 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -15,6 +15,7 @@ public class EzySslHandshakeHandler extends EzyLoggable { private final SSLContext sslContext; + private final int timeout; @SuppressWarnings("MethodLength") public void handle(SocketChannel socketChannel) throws IOException { @@ -33,10 +34,13 @@ public void handle(SocketChannel socketChannel) throws IOException { SSLEngineResult result; SSLException sslException = null; SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + long currentTime = System.currentTimeMillis(); + long endTime = currentTime + timeout; while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { switch (handshakeStatus) { case NEED_UNWRAP: - if (socketChannel.read(peerNetData) < 0) { + int readBytes = socketChannel.read(peerNetData); + if (readBytes < 0) { if (engine.isInboundDone() && engine.isOutboundDone()) { throw new SSLException( "status is NEED_UNWRAP " + @@ -91,7 +95,7 @@ public void handle(SocketChannel socketChannel) throws IOException { break; } default: - throw new IllegalStateException( + throw new SSLException( "Invalid SSL status: " + result.getStatus() ); } @@ -116,7 +120,10 @@ public void handle(SocketChannel socketChannel) throws IOException { case OK : netBuffer.flip(); while (netBuffer.hasRemaining()) { - socketChannel.write(netBuffer); + int writeBytes = socketChannel.write(netBuffer); + if (writeBytes < 0) { + throw new SSLException("Maybe client closed."); + } } break; case BUFFER_OVERFLOW: @@ -128,7 +135,10 @@ public void handle(SocketChannel socketChannel) throws IOException { try { netBuffer.flip(); while (netBuffer.hasRemaining()) { - socketChannel.write(netBuffer); + int writeBytes = socketChannel.write(netBuffer); + if (writeBytes < 0) { + throw new SSLException("Maybe client closed."); + } } peerNetData.clear(); } catch (Exception e) { @@ -153,7 +163,11 @@ public void handle(SocketChannel socketChannel) throws IOException { handshakeStatus = engine.getHandshakeStatus(); break; default: - throw new IllegalStateException("Invalid SSL status: " + handshakeStatus); + throw new SSLException("Invalid SSL status: " + handshakeStatus); + } + currentTime = System.currentTimeMillis(); + if (currentTime >= endTime) { + throw new SSLException("Timeout"); } } if (sslException != null) { diff --git a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd index e863e1d8..8660b650 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd +++ b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd @@ -64,9 +64,12 @@ + + +
diff --git a/ezyfox-server-core/src/main/resources/ezy-settings.xml b/ezyfox-server-core/src/main/resources/ezy-settings.xml index e7c207cc..1481335b 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-settings.xml @@ -28,8 +28,11 @@ true true L7 + 300 true 4096 + 1 + 8 8 com.tvd12.ezyfoxserver.netty.codec.MsgPackCodecCreator diff --git a/ezyfox-server-embedded/pom.xml b/ezyfox-server-embedded/pom.xml index 78f7c9ca..4a49733a 100755 --- a/ezyfox-server-embedded/pom.xml +++ b/ezyfox-server-embedded/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyfox-server - 1.2.8 + 1.2.8.1 ezyfox-server-embedded @@ -26,12 +26,12 @@ com.tvd12 ezyfox-msgpack - ${project.version} + ${ezy.version} com.tvd12 ezyfox-jackson - ${project.version} + ${ezy.version} diff --git a/ezyfox-server-nio/pom.xml b/ezyfox-server-nio/pom.xml index 2c7301f0..b831bbe4 100755 --- a/ezyfox-server-nio/pom.xml +++ b/ezyfox-server-nio/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyfox-server - 1.2.8 + 1.2.8.1 ezyfox-server-nio diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index b9094928..bbae159b 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -16,7 +16,6 @@ import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; -import java.util.ArrayList; import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; @@ -84,10 +83,20 @@ private void startSocketHandlers() throws Exception { } private EzyNioSocketAcceptor newSocketAcceptor() { - EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); + EzyNioSocketAcceptor acceptor; if (isEnableL4Ssl()) { + acceptor = new EzyNioSocketAcceptor( + getSslConnectionAcceptorThreadPoolSize() + ); acceptor.setSslHandshakeHandler( - new EzySslHandshakeHandler(sslContext) + new EzySslHandshakeHandler( + sslContext, + getSslHandshakeTimeout() + ) + ); + } else { + acceptor = new EzyNioSocketAcceptor( + getConnectionAcceptorThreadPoolSize() ); } return acceptor; @@ -95,7 +104,7 @@ private EzyNioSocketAcceptor newSocketAcceptor() { private EzySocketEventLoopHandler newWritingLoopHandler() { EzySocketWritingLoopHandler loopHandler = new EzySocketWritingLoopHandler(); - loopHandler.setThreadPoolSize(getSocketWriterPoolSize()); + loopHandler.setThreadPoolSize(getSocketWriterThreadPoolSize()); loopHandler.setEventHandlerSupplier(() -> { EzySocketWriter eventHandler = new EzyNioSocketWriter(); eventHandler.setWriterGroupFetcher(handlerGroupManager); @@ -109,7 +118,7 @@ private EzySocketEventLoopHandler newReadingLoopHandler( EzyNioAcceptableConnectionsHandler acceptableConnectionsHandler ) { EzySocketEventLoopOneHandler loopHandler = new EzyNioSocketReadingLoopHandler(); - loopHandler.setThreadPoolSize(getSocketReaderPoolSize()); + loopHandler.setThreadPoolSize(getSocketReaderThreadPoolSize()); EzyNioSocketReader eventHandler = new EzyNioSocketReader(); eventHandler.setOwnSelector(readSelector); eventHandler.setSocketDataReceiver(socketDataReceiver); @@ -121,11 +130,10 @@ private EzySocketEventLoopHandler newReadingLoopHandler( private EzySocketEventLoopHandler newSocketAcceptanceLoopHandler( EzyNioSocketAcceptor socketAcceptor) { EzySocketEventLoopOneHandler loopHandler = new EzyNioSocketAcceptanceLoopHandler(); - loopHandler.setThreadPoolSize(getSocketAcceptorPoolSize()); + loopHandler.setThreadPoolSize(getSocketAcceptorThreadPoolSize()); socketAcceptor.setTcpNoDelay(getSocketTcpNoDelay()); socketAcceptor.setReadSelector(readSelector); socketAcceptor.setOwnSelector(acceptSelector); - socketAcceptor.setAcceptableConnections(new ArrayList<>()); socketAcceptor.setHandlerGroupManager(handlerGroupManager); loopHandler.setEventHandler(socketAcceptor); return loopHandler; @@ -144,15 +152,27 @@ private boolean isEnableL4Ssl() { return setting.isSslActive() && setting.getSslType() == SslType.L4; } - private int getSocketReaderPoolSize() { + public int getSslHandshakeTimeout() { + return getSocketSetting().getSslHandshakeTimeout(); + } + + private int getSocketReaderThreadPoolSize() { return EzyNioThreadPoolSizes.SOCKET_READER; } - private int getSocketWriterPoolSize() { + private int getSocketWriterThreadPoolSize() { return getSocketSetting().getWriterThreadPoolSize(); } - private int getSocketAcceptorPoolSize() { + private int getConnectionAcceptorThreadPoolSize() { + return getSocketSetting().getConnectionAcceptorThreadPoolSize(); + } + + private int getSslConnectionAcceptorThreadPoolSize() { + return getSocketSetting().getSslConnectionAcceptorThreadPoolSize(); + } + + private int getSocketAcceptorThreadPoolSize() { return EzyNioThreadPoolSizes.SOCKET_ACCEPTOR; } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioAcceptableConnectionsHandler.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioAcceptableConnectionsHandler.java index d0893c2d..3098807d 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioAcceptableConnectionsHandler.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioAcceptableConnectionsHandler.java @@ -2,5 +2,5 @@ public interface EzyNioAcceptableConnectionsHandler { - void handleAcceptableConnections(); + void handleAcceptableConnections() throws Exception; } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index 12eb20fc..5e3ee53d 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -1,5 +1,6 @@ package com.tvd12.ezyfoxserver.nio.socket; +import com.tvd12.ezyfox.concurrent.EzyExecutors; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.nio.entity.EzyNioSession; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; @@ -14,9 +15,12 @@ import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; @@ -31,11 +35,23 @@ public class EzyNioSocketAcceptor @Setter protected Selector readSelector; @Setter - protected List acceptableConnections; - @Setter protected EzyHandlerGroupManager handlerGroupManager; @Setter protected EzySslHandshakeHandler sslHandshakeHandler; + protected final List acceptableConnections; + protected final List acceptableConnectionsBuffer; + protected final ExecutorService connectionAcceptorExecutorService; + + public EzyNioSocketAcceptor( + int connectionAcceptorThreadPoolSize + ) { + acceptableConnections = new ArrayList<>(); + acceptableConnectionsBuffer = new ArrayList<>(); + connectionAcceptorExecutorService = EzyExecutors.newFixedThreadPool( + connectionAcceptorThreadPoolSize, + "connection-acceptor" + ); + } @Override public void destroy() { @@ -51,12 +67,24 @@ public void handleEvent() { } } - public void handleAcceptableConnections() { - if (acceptableConnections.size() > 0) { - //noinspection SynchronizeOnNonFinalField - synchronized (acceptableConnections) { - doHandleAcceptableConnections(); + public void handleAcceptableConnections() throws Exception { + synchronized (acceptableConnections) { + acceptableConnectionsBuffer.addAll(acceptableConnections); + acceptableConnections.clear(); + } + CountDownLatch countDownLatch = new CountDownLatch( + acceptableConnectionsBuffer.size() + ); + try { + for (SocketChannel clientChannel : acceptableConnectionsBuffer) { + connectionAcceptorExecutorService.execute(() -> { + acceptConnection(clientChannel); + countDownLatch.countDown(); + }); } + countDownLatch.await(); + } finally { + acceptableConnectionsBuffer.clear(); } } @@ -82,25 +110,15 @@ private void processReadyKey(SelectionKey key) throws Exception { } private void addConnection(SocketChannel clientChannel) { - //noinspection SynchronizeOnNonFinalField synchronized (acceptableConnections) { acceptableConnections.add(clientChannel); } } - private void doHandleAcceptableConnections() { - Iterator iterator = acceptableConnections.iterator(); - while (iterator.hasNext()) { - SocketChannel clientChannel = iterator.next(); - iterator.remove(); - acceptConnection(clientChannel); - } - } - private void acceptConnection(SocketChannel clientChannel) { try { doAcceptConnection(clientChannel); - } catch (Exception e) { + } catch (Throwable e) { logger.info("can't accept connection: {}", clientChannel, e); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java index cf828514..c809cc19 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java @@ -36,7 +36,7 @@ public void handleEvent() { } } - private void handleAcceptableConnections() { + private void handleAcceptableConnections() throws Exception { acceptableConnectionsHandler.handleAcceptableConnections(); } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java index f18f7d45..961d92cb 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java @@ -37,7 +37,6 @@ import java.nio.channels.SocketChannel; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.SelectorProvider; -import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.ExecutorService; @@ -70,8 +69,7 @@ public void test() throws Exception { channel1.configureBlocking(false); when(channel1.register(readSelector, SelectionKey.OP_READ)).thenReturn(selectionKey1); - EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); - acceptor.setAcceptableConnections(new ArrayList<>()); + EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(1); acceptor.setHandlerGroupManager(handlerGroupManager); acceptor.setOwnSelector(ownSelector); acceptor.setReadSelector(readSelector); @@ -101,8 +99,7 @@ public void acceptConnectionExceptionCase() throws Exception { Selector readSelector = spy(ExSelector.class); - EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); - acceptor.setAcceptableConnections(new ArrayList<>()); + EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(1); acceptor.setHandlerGroupManager(handlerGroupManager); acceptor.setOwnSelector(ownSelector); acceptor.setReadSelector(readSelector); @@ -165,7 +162,7 @@ public void acceptConnectionTest() throws Exception { EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); Selector readSelector = Selector.open(); - EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(); + EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(1); sut.setReadSelector(readSelector); SocketChannel clientChannel = SocketChannel.open(); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java index 83e441a0..90d2fbe6 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java @@ -35,7 +35,6 @@ import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import java.util.ArrayList; import java.util.Queue; import java.util.concurrent.ExecutorService; @@ -100,10 +99,9 @@ public void test() throws Exception { when(socketChannel5.isConnected()).thenReturn(true); when(socketChannel5.read(any(ByteBuffer.class))).then((Answer) invocation -> -1); - EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); + EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(1); socketAcceptor.setReadSelector(ownSelector); socketAcceptor.setHandlerGroupManager(handlerGroupManager); - socketAcceptor.setAcceptableConnections(new ArrayList<>()); EzyNioSocketReader socketReader = new EzyNioSocketReader(); socketReader.setOwnSelector(ownSelector); @@ -138,10 +136,9 @@ public void testExceptionCase() throws Exception { throw new IllegalStateException("server maintain"); }); - EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); + EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(1); socketAcceptor.setReadSelector(ownSelector); socketAcceptor.setHandlerGroupManager(handlerGroupManager); - socketAcceptor.setAcceptableConnections(new ArrayList<>()); EzyNioSocketReader socketReader = new EzyNioSocketReader(); socketReader.setOwnSelector(ownSelector); diff --git a/ezyfox-server-niorunner/pom.xml b/ezyfox-server-niorunner/pom.xml index b6ccb6e0..5266961f 100755 --- a/ezyfox-server-niorunner/pom.xml +++ b/ezyfox-server-niorunner/pom.xml @@ -5,7 +5,7 @@ com.tvd12 ezyfox-server - 1.2.8 + 1.2.8.1 ezyfox-server-niorunner diff --git a/ezyfox-server-support/pom.xml b/ezyfox-server-support/pom.xml index fddc52cd..c575fd01 100644 --- a/ezyfox-server-support/pom.xml +++ b/ezyfox-server-support/pom.xml @@ -4,7 +4,7 @@ com.tvd12 ezyfox-server - 1.2.8 + 1.2.8.1 ezyfox-server-support ezyfox-server-support @@ -19,12 +19,12 @@ com.tvd12 ezyfox-bean - ${project.version} + ${ezy.version} com.tvd12 ezyfox-binding - ${project.version} + ${ezy.version} com.tvd12 diff --git a/pom.xml b/pom.xml index 5dcea273..85f8e980 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0.6 ezyfox-server - 1.2.8 + 1.2.8.1 pom ezyfox-server @@ -57,6 +57,7 @@ + 1.2.8 2.1.7 From dc090bdab97f9cbf39708bc3226802d3e5fe7c37 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Wed, 12 Jul 2023 17:40:22 +0700 Subject: [PATCH 08/46] can transfer data with swift --- .../controller/EzyHandshakeController.java | 8 +- .../setting/EzySocketSetting.java | 8 ++ .../ssl/EzySslHandshakeHandler.java | 36 ++------ .../ezyfoxserver/ssl/SslByteBuffers.java | 38 +++++++++ .../nio/EzySocketServerBootstrap.java | 3 +- .../EzyNioServerBootstrapBuilderImpl.java | 9 +- .../nio/handler/EzyAbstractHandlerGroup.java | 2 + .../nio/handler/EzyNioHandlerGroup.java | 3 + .../nio/socket/EzyNioSecureSocketChannel.java | 53 ++++++++++++ .../nio/socket/EzyNioSocketAcceptor.java | 10 ++- .../nio/socket/EzyNioSocketChannel.java | 6 +- .../socket/EzySecureSocketDataReceiver.java | 84 +++++++++++++++++++ .../nio/socket/EzySocketDataReceiver.java | 37 +++++--- 13 files changed, 245 insertions(+), 52 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java create mode 100644 ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java create mode 100644 ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java index b633d5c5..16cb97b7 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java @@ -39,8 +39,12 @@ protected void handleSocketSSL(EzyServerContext ctx, EzyHandshakeEvent event) { if (session.getConnectionType() == EzyConnectionType.WEBSOCKET) { return; } - boolean enableSSL = ctx.getServer().getSettings().getSocket().isSslActive(); - if (!enableSSL) { + boolean enableL7SSL = ctx + .getServer() + .getSettings() + .getSocket() + .isEnableL7Ssl(); + if (!enableL7SSL) { return; } if (!event.isEnableEncryption()) { diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 24398663..6dc3399e 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java @@ -16,4 +16,12 @@ public interface EzySocketSetting extends EzyBaseSocketSetting { int getSslConnectionAcceptorThreadPoolSize(); int getWriterThreadPoolSize(); + + default boolean isEnableL4Ssl() { + return isSslActive() && getSslType() == SslType.L4; + } + + default boolean isEnableL7Ssl() { + return isSslActive() && getSslType() == SslType.L7; + } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index 04ab3cf6..0dc60b3b 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -8,6 +8,8 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeApplicationBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargePacketBuffer; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; @@ -18,7 +20,9 @@ public class EzySslHandshakeHandler extends EzyLoggable { private final int timeout; @SuppressWarnings("MethodLength") - public void handle(SocketChannel socketChannel) throws IOException { + public SSLEngine handle( + SocketChannel socketChannel + ) throws IOException { SSLEngine engine = sslContext.createSSLEngine(); engine.setUseClientMode(false); engine.beginHandshake(); @@ -173,6 +177,7 @@ public void handle(SocketChannel socketChannel) throws IOException { if (sslException != null) { throw sslException; } + return engine; } protected ByteBuffer handleBufferUnderflow( @@ -188,33 +193,4 @@ protected ByteBuffer handleBufferUnderflow( return replaceBuffer; } } - - protected ByteBuffer enlargePacketBuffer( - SSLEngine engine, - ByteBuffer buffer - ) { - return enlargeBuffer( - buffer, - engine.getSession().getPacketBufferSize() - ); - } - - protected ByteBuffer enlargeApplicationBuffer( - SSLEngine engine, - ByteBuffer buffer - ) { - return enlargeBuffer( - buffer, - engine.getSession().getApplicationBufferSize() - ); - } - - protected ByteBuffer enlargeBuffer( - ByteBuffer buffer, - int sessionProposedCapacity - ) { - return sessionProposedCapacity > buffer.capacity() - ? ByteBuffer.allocate(sessionProposedCapacity) - : ByteBuffer.allocate(buffer.capacity() * 2); - } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java new file mode 100644 index 00000000..4cf8164e --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java @@ -0,0 +1,38 @@ +package com.tvd12.ezyfoxserver.ssl; + +import javax.net.ssl.SSLEngine; +import java.nio.ByteBuffer; + +public final class SslByteBuffers { + + private SslByteBuffers() {} + + public static ByteBuffer enlargePacketBuffer( + SSLEngine engine, + ByteBuffer buffer + ) { + return enlargeBuffer( + buffer, + engine.getSession().getPacketBufferSize() + ); + } + + public static ByteBuffer enlargeApplicationBuffer( + SSLEngine engine, + ByteBuffer buffer + ) { + return enlargeBuffer( + buffer, + engine.getSession().getApplicationBufferSize() + ); + } + + public static ByteBuffer enlargeBuffer( + ByteBuffer buffer, + int sessionProposedCapacity + ) { + return sessionProposedCapacity > buffer.capacity() + ? ByteBuffer.allocate(sessionProposedCapacity) + : ByteBuffer.allocate(buffer.capacity() * 2); + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index bbae159b..0e96e553 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -1,6 +1,5 @@ package com.tvd12.ezyfoxserver.nio; -import com.tvd12.ezyfoxserver.constant.SslType; import com.tvd12.ezyfoxserver.nio.constant.EzyNioThreadPoolSizes; import com.tvd12.ezyfoxserver.nio.socket.*; import com.tvd12.ezyfoxserver.setting.EzySocketSetting; @@ -149,7 +148,7 @@ private ServerSocketChannel newServerSocketChannel() throws Exception { private boolean isEnableL4Ssl() { EzySocketSetting setting = getSocketSetting(); - return setting.isSslActive() && setting.getSslType() == SslType.L4; + return setting.isEnableL4Ssl(); } public int getSslHandshakeTimeout() { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index f62a22c6..9f6ee732 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -12,9 +12,11 @@ import com.tvd12.ezyfoxserver.nio.EzyNioServerBootstrap; import com.tvd12.ezyfoxserver.nio.builder.EzyNioServerBootstrapBuilder; import com.tvd12.ezyfoxserver.nio.factory.EzyHandlerGroupBuilderFactory; +import com.tvd12.ezyfoxserver.nio.socket.EzySecureSocketDataReceiver; import com.tvd12.ezyfoxserver.nio.socket.EzySocketDataReceiver; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; import com.tvd12.ezyfoxserver.nio.wrapper.impl.EzyHandlerGroupManagerImpl; +import com.tvd12.ezyfoxserver.setting.EzySocketSetting; import com.tvd12.ezyfoxserver.socket.*; import java.util.concurrent.ExecutorService; @@ -103,7 +105,12 @@ private ExecutorService newStatsThreadPool() { } private EzySocketDataReceiver newSocketDataReceiver(EzyHandlerGroupManager handlerGroupManager) { - return EzySocketDataReceiver.builder() + EzySocketSetting setting = getSocketSetting(); + return ( + setting.isEnableL4Ssl() + ? EzySecureSocketDataReceiver.builder() + : EzySocketDataReceiver.builder() + ) .handlerGroupManager(handlerGroupManager) .threadPoolSize(getThreadPoolSizeSetting().getSocketDataReceiver()) .build(); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java index d23c6fa3..083d5337 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java @@ -16,6 +16,7 @@ import com.tvd12.ezyfoxserver.socket.*; import com.tvd12.ezyfoxserver.statistics.EzyNetworkStats; import com.tvd12.ezyfoxserver.statistics.EzySessionStats; +import lombok.Getter; import java.net.SocketAddress; import java.nio.ByteBuffer; @@ -32,6 +33,7 @@ public abstract class EzyAbstractHandlerGroup EzyDroppedPackets, EzyDestroyable { + @Getter protected final EzyChannel channel; protected final D decoder; diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyNioHandlerGroup.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyNioHandlerGroup.java index fe4756eb..b7314116 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyNioHandlerGroup.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyNioHandlerGroup.java @@ -2,6 +2,7 @@ import com.tvd12.ezyfox.codec.EzyMessage; import com.tvd12.ezyfoxserver.nio.entity.EzyNioSession; +import com.tvd12.ezyfoxserver.socket.EzyChannel; public interface EzyNioHandlerGroup extends EzyHandlerGroup { @@ -9,5 +10,7 @@ public interface EzyNioHandlerGroup extends EzyHandlerGroup { void fireMessageReceived(EzyMessage message) throws Exception; + EzyChannel getChannel(); + EzyNioSession getSession(); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java new file mode 100644 index 00000000..da1b2c44 --- /dev/null +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -0,0 +1,53 @@ +package com.tvd12.ezyfoxserver.nio.socket; + +import lombok.Getter; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargePacketBuffer; + +public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { + + private ByteBuffer netBuffer; + @Getter + private final SSLEngine engine; + + public EzyNioSecureSocketChannel( + SocketChannel channel, + SSLEngine engine + ) { + super(channel); + this.engine = engine; + SSLSession session = engine.getSession(); + int packetBufferSize = session.getPacketBufferSize(); + this.netBuffer = ByteBuffer.allocate(packetBufferSize); + } + + @Override + public int write(Object data, boolean binary) throws Exception { + ByteBuffer dataToWrite = (ByteBuffer) data; + netBuffer.clear(); + SSLEngineResult result = engine.wrap(dataToWrite, netBuffer); + switch (result.getStatus()) { + case OK: + netBuffer.flip(); + return channel.write(netBuffer); + case BUFFER_OVERFLOW: + netBuffer = enlargePacketBuffer(engine, netBuffer); + return 0; + case BUFFER_UNDERFLOW: + throw new IOException("Buffer underflow occurred after a wrap"); + case CLOSED: + engine.closeOutbound(); + channel.close(); + return -1; + default: + throw new IOException("Invalid SSL status: " + result.getStatus()); + } + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index 5e3ee53d..adc65de3 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -11,6 +11,8 @@ import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import lombok.Setter; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -36,6 +38,7 @@ public class EzyNioSocketAcceptor protected Selector readSelector; @Setter protected EzyHandlerGroupManager handlerGroupManager; + protected SSLContext sslContext; @Setter protected EzySslHandshakeHandler sslHandshakeHandler; protected final List acceptableConnections; @@ -126,15 +129,18 @@ private void acceptConnection(SocketChannel clientChannel) { private void doAcceptConnection(SocketChannel clientChannel) throws Exception { clientChannel.configureBlocking(false); clientChannel.socket().setTcpNoDelay(tcpNoDelay); + SSLEngine sslEngine = null; try { if (sslHandshakeHandler != null) { - sslHandshakeHandler.handle(clientChannel); + sslEngine = sslHandshakeHandler.handle(clientChannel); } } catch (Exception e) { clientChannel.close(); throw e; } - EzyChannel channel = new EzyNioSocketChannel(clientChannel); + EzyChannel channel = sslEngine == null + ? new EzyNioSocketChannel(clientChannel) + : new EzyNioSecureSocketChannel(clientChannel, sslEngine); EzyNioHandlerGroup handlerGroup = handlerGroupManager .newHandlerGroup(channel, EzyConnectionType.SOCKET); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketChannel.java index 87a77e2e..6cf10be6 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketChannel.java @@ -14,9 +14,9 @@ @Getter public class EzyNioSocketChannel implements EzyChannel { - private final SocketChannel channel; - private final SocketAddress serverAddress; - private final SocketAddress clientAddress; + protected final SocketChannel channel; + protected final SocketAddress serverAddress; + protected final SocketAddress clientAddress; public EzyNioSocketChannel(SocketChannel channel) { this.channel = channel; diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java new file mode 100644 index 00000000..3bf523bf --- /dev/null +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -0,0 +1,84 @@ +package com.tvd12.ezyfoxserver.nio.socket; + +import com.tvd12.ezyfoxserver.socket.EzyChannel; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import java.io.IOException; +import java.nio.ByteBuffer; + +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeApplicationBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargePacketBuffer; + +public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { + + protected final ByteBuffer[] tcpNetBuffers; + + public EzySecureSocketDataReceiver(Builder builder) { + super(builder); + this.tcpNetBuffers = newTcpByteBuffers(threadPoolSize); + } + + @Override + protected byte[] readTcpBytesFromBuffer( + EzyChannel channel, + ByteBuffer buffer + ) throws Exception { + EzyNioSecureSocketChannel secureChannel = + (EzyNioSecureSocketChannel) channel; + SSLEngine engine = secureChannel.getEngine(); + int index = Math.abs(channel.hashCode() % threadPoolSize); + ByteBuffer tcpNetBuffer = tcpNetBuffers[index]; + while (buffer.hasRemaining()) { + tcpNetBuffer.clear(); + SSLEngineResult result = engine.unwrap(buffer, tcpNetBuffer); + switch (result.getStatus()) { + case OK: + tcpNetBuffer.flip(); + byte[] binary = new byte[tcpNetBuffer.limit()]; + tcpNetBuffer.get(binary); + return binary; + case BUFFER_OVERFLOW: + tcpNetBuffer = enlargeApplicationBuffer(engine, tcpNetBuffer); + break; + case BUFFER_UNDERFLOW: + tcpNetBuffer = handleBufferUnderflow(engine, tcpNetBuffer); + break; + case CLOSED: + engine.closeOutbound(); + channel.close(); + throw new IOException("connection close"); + default: + throw new IOException( + "Invalid SSL status: " + result.getStatus() + ); + } + } + return new byte[0]; + } + + protected ByteBuffer handleBufferUnderflow( + SSLEngine engine, + ByteBuffer buffer) { + if (engine.getSession().getPacketBufferSize() < buffer.limit()) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer); + buffer.flip(); + replaceBuffer.put(buffer); + return replaceBuffer; + } + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends EzySocketDataReceiver.Builder { + + @Override + public EzySocketDataReceiver build() { + return new EzySecureSocketDataReceiver(this); + } + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java index 0507047d..08d522a0 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java @@ -10,6 +10,7 @@ import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.nio.websocket.EzyWsHandlerGroup; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; +import com.tvd12.ezyfoxserver.socket.EzyChannel; import org.eclipse.jetty.websocket.api.Session; import java.nio.ByteBuffer; @@ -33,11 +34,7 @@ public EzySocketDataReceiver(Builder builder) { this.executorServices = newExecutorServices(threadPoolSize); } - public static Builder builder() { - return new Builder(); - } - - private ByteBuffer[] newTcpByteBuffers(int size) { + protected ByteBuffer[] newTcpByteBuffers(int size) { ByteBuffer[] answer = new ByteBuffer[size]; for (int i = 0; i < size; ++i) { answer[i] = ByteBuffer.allocateDirect(getMaxBufferSize()); @@ -90,25 +87,37 @@ private void tcpReadBytes( if (readBytes == -1) { tcpCloseConnection(channel); } else if (readBytes > 0) { - processReadBytes(channel, buffer); + processTcpReadBytes(channel, buffer); } if (exception != null) { throw exception; } } - private void processReadBytes( + private void processTcpReadBytes( SocketChannel channel, ByteBuffer buffer ) throws Exception { - buffer.flip(); - byte[] binary = new byte[buffer.limit()]; - buffer.get(binary); EzyNioHandlerGroup handlerGroup = handlerGroupManager.getHandlerGroup(channel); - if (handlerGroup != null) { - handlerGroup.fireBytesReceived(binary); + if (handlerGroup == null) { + return; } + buffer.flip(); + byte[] binary = readTcpBytesFromBuffer( + handlerGroup.getChannel(), + buffer + ); + handlerGroup.fireBytesReceived(binary); + } + + protected byte[] readTcpBytesFromBuffer( + EzyChannel channel, + ByteBuffer buffer + ) throws Exception { + byte[] binary = new byte[buffer.limit()]; + buffer.get(binary); + return binary; } private void tcpCloseConnection(SocketChannel channel) { @@ -209,6 +218,10 @@ private int getMaxBufferSize() { return EzyCoreConstants.MAX_READ_BUFFER_SIZE; } + public static Builder builder() { + return new Builder(); + } + public static class Builder implements EzyBuilder { protected int threadPoolSize; protected EzyHandlerGroupManager handlerGroupManager; From a66f9919f0eb227ae40ae85ab3c29bac68a1b269 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Wed, 12 Jul 2023 22:46:50 +0700 Subject: [PATCH 09/46] add EzyChannel.pack --- .../api/EzyAbstractResponseApi.java | 44 ++++++++++++--- .../api/EzySocketResponseApi.java | 34 ++++++++++-- .../controller/EzyHandshakeController.java | 2 +- .../setting/EzySocketSetting.java | 4 +- .../tvd12/ezyfoxserver/socket/EzyChannel.java | 4 ++ .../EzyHandShakeControllerTest.java | 3 +- .../nio/EzySocketServerBootstrap.java | 2 +- .../EzyNioServerBootstrapBuilderImpl.java | 2 +- .../nio/socket/EzyNioSecureSocketChannel.java | 54 ++++++++++--------- .../socket/EzySocketDataReceiverTest.java | 2 +- 10 files changed, 108 insertions(+), 43 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java index f6baa35b..1f19d780 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java @@ -2,13 +2,16 @@ import com.tvd12.ezyfox.constant.EzyConstant; import com.tvd12.ezyfox.entity.EzyArray; +import com.tvd12.ezyfox.util.EzyLoggable; import com.tvd12.ezyfoxserver.entity.EzySession; import com.tvd12.ezyfoxserver.response.EzyPackage; import com.tvd12.ezyfoxserver.socket.EzySimplePacket; import java.util.Collection; -public abstract class EzyAbstractResponseApi implements EzyResponseApi { +public abstract class EzyAbstractResponseApi + extends EzyLoggable + implements EzyResponseApi { @Override public void response( @@ -34,11 +37,21 @@ protected final void normalResponse( Object bytes = encodeData(pack.getData()); if (immediate) { for (EzySession session : recipients) { - session.sendNow(createPacket(bytes, pack)); + try { + Object packedBytes = packMessage(session, bytes); + session.sendNow(createPacket(packedBytes, pack)); + } catch (Throwable e) { + logger.info("response data now to session: {} failed", session, e); + } } } else { for (EzySession session : recipients) { - session.send(createPacket(bytes, pack)); + try { + Object packedBytes = packMessage(session, bytes); + session.send(createPacket(packedBytes, pack)); + } catch (Throwable e) { + logger.info("response data to session: {} failed", session, e); + } } } } @@ -55,13 +68,23 @@ protected final void secureResponse( byte[] messageContent = dataToMessageContent(pack.getData()); if (immediate) { for (EzySession session : recipients) { - byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - session.sendNow(createPacket(bytes, pack)); + try { + byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); + Object packedBytes = packMessage(session, bytes); + session.sendNow(createPacket(packedBytes, pack)); + } catch (Throwable e) { + logger.info("response data now to session: {} failed", session, e); + } } } else { for (EzySession session : recipients) { - byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - session.send(createPacket(bytes, pack)); + try { + byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); + Object packedBytes = packMessage(session, bytes); + session.send(createPacket(packedBytes, pack)); + } catch (Throwable e) { + logger.info("response data to session: {} failed", session, e); + } } } } @@ -87,4 +110,11 @@ protected byte[] encryptMessageContent( ) throws Exception { throw new UnsupportedOperationException("unsupported"); } + + protected Object packMessage( + EzySession session, + Object message + ) throws Exception { + return message; + } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java index 400ddad4..b92060bb 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java @@ -6,29 +6,53 @@ import com.tvd12.ezyfox.constant.EzyConstant; import com.tvd12.ezyfox.entity.EzyArray; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; +import com.tvd12.ezyfoxserver.entity.EzySession; +import com.tvd12.ezyfoxserver.socket.EzyChannel; public class EzySocketResponseApi extends EzyAbstractResponseApi { protected final EzyMessageDataEncoder encoder; public EzySocketResponseApi(Object encoder) { - this.encoder = new EzySimpleMessageDataEncoder((EzyObjectToByteEncoder) encoder); + this.encoder = new EzySimpleMessageDataEncoder( + (EzyObjectToByteEncoder) encoder + ); } @Override - protected Object encodeData(EzyArray data) throws Exception { + protected Object encodeData( + EzyArray data + ) throws Exception { return encoder.encode(data); } @Override - protected byte[] dataToMessageContent(EzyArray data) throws Exception { + protected byte[] dataToMessageContent( + EzyArray data + ) throws Exception { return encoder.toMessageContent(data); } @Override protected byte[] encryptMessageContent( - byte[] messageContent, byte[] encryptionKey) throws Exception { - return encoder.encryptMessageContent(messageContent, encryptionKey); + byte[] messageContent, + byte[] encryptionKey + ) throws Exception { + return encoder.encryptMessageContent( + messageContent, + encryptionKey + ); + } + + @Override + protected Object packMessage( + EzySession session, + Object message + ) throws Exception { + EzyChannel channel = session.getChannel(); + return channel == null + ? message + : channel.pack((byte[]) message); } @Override diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java index 16cb97b7..a9597fcd 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java @@ -43,7 +43,7 @@ protected void handleSocketSSL(EzyServerContext ctx, EzyHandshakeEvent event) { .getServer() .getSettings() .getSocket() - .isEnableL7Ssl(); + .isL7SslActive(); if (!enableL7SSL) { return; } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 6dc3399e..8e8cc2c9 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java @@ -17,11 +17,11 @@ public interface EzySocketSetting extends EzyBaseSocketSetting { int getWriterThreadPoolSize(); - default boolean isEnableL4Ssl() { + default boolean isL4SslActive() { return isSslActive() && getSslType() == SslType.L4; } - default boolean isEnableL7Ssl() { + default boolean isL7SslActive() { return isSslActive() && getSslType() == SslType.L7; } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java index 48fb22d6..f20d2b40 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java @@ -21,4 +21,8 @@ public interface EzyChannel { SocketAddress getServerAddress(); SocketAddress getClientAddress(); + + default byte[] pack(byte[] bytes) throws Exception { + return bytes; + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java index 13c2ce63..4794ed0c 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java @@ -72,7 +72,7 @@ public void handleSocketSSLTest() { EzySettings settings = mock(EzySettings.class); EzySocketSetting socketSetting = mock(EzySocketSetting.class); when(settings.getSocket()).thenReturn(socketSetting); - when(socketSetting.isSslActive()).thenReturn(true); + when(socketSetting.isL7SslActive()).thenReturn(true); when(serverContext.getServer()).thenReturn(server); when(server.getSettings()).thenReturn(settings); @@ -95,6 +95,7 @@ public void handleSocketSSLTest() { sut.handle(serverContext, request); // then + verify(socketSetting, times(1)).isL7SslActive(); verify(session, times(1)).setClientId(clientId); verify(session, times(1)).setClientKey(clientKey); verify(session, times(1)).setClientType(clientType); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 0e96e553..b77eec56 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -148,7 +148,7 @@ private ServerSocketChannel newServerSocketChannel() throws Exception { private boolean isEnableL4Ssl() { EzySocketSetting setting = getSocketSetting(); - return setting.isEnableL4Ssl(); + return setting.isL4SslActive(); } public int getSslHandshakeTimeout() { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index 9f6ee732..c1b19c58 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -107,7 +107,7 @@ private ExecutorService newStatsThreadPool() { private EzySocketDataReceiver newSocketDataReceiver(EzyHandlerGroupManager handlerGroupManager) { EzySocketSetting setting = getSocketSetting(); return ( - setting.isEnableL4Ssl() + setting.isL4SslActive() ? EzySecureSocketDataReceiver.builder() : EzySocketDataReceiver.builder() ) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index da1b2c44..91b46fbc 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -4,7 +4,6 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLSession; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -13,7 +12,6 @@ public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { - private ByteBuffer netBuffer; @Getter private final SSLEngine engine; @@ -23,31 +21,39 @@ public EzyNioSecureSocketChannel( ) { super(channel); this.engine = engine; - SSLSession session = engine.getSession(); - int packetBufferSize = session.getPacketBufferSize(); - this.netBuffer = ByteBuffer.allocate(packetBufferSize); } @Override - public int write(Object data, boolean binary) throws Exception { - ByteBuffer dataToWrite = (ByteBuffer) data; - netBuffer.clear(); - SSLEngineResult result = engine.wrap(dataToWrite, netBuffer); - switch (result.getStatus()) { - case OK: - netBuffer.flip(); - return channel.write(netBuffer); - case BUFFER_OVERFLOW: - netBuffer = enlargePacketBuffer(engine, netBuffer); - return 0; - case BUFFER_UNDERFLOW: - throw new IOException("Buffer underflow occurred after a wrap"); - case CLOSED: - engine.closeOutbound(); - channel.close(); - return -1; - default: - throw new IOException("Invalid SSL status: " + result.getStatus()); + public byte[] pack(byte[] bytes) throws Exception { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + int netBufferLength = engine + .getSession() + .getPacketBufferSize(); + ByteBuffer netBuffer = ByteBuffer.allocate(netBufferLength); + while (buffer.hasRemaining()) { + SSLEngineResult result = engine.wrap( + buffer, + netBuffer + ); + switch (result.getStatus()) { + case OK: + netBuffer.flip(); + byte[] answer = new byte[netBuffer.limit()]; + netBuffer.get(answer); + return answer; + case BUFFER_OVERFLOW: + netBuffer = enlargePacketBuffer(engine, netBuffer); + break; + case BUFFER_UNDERFLOW: + throw new IOException("Buffer underflow occurred after a wrap"); + case CLOSED: + engine.closeOutbound(); + channel.close(); + break; + default: + throw new IOException("Invalid SSL status: " + result.getStatus()); + } } + return bytes; } } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java index 86d82db4..6f7c72ad 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java @@ -59,7 +59,7 @@ public void processReadBytesHandlerGroupIsNull() throws Exception { // when MethodInvoker.create() .object(sut) - .method("processReadBytes") + .method("processTcpReadBytes") .param(SocketChannel.class, channel) .param(ByteBuffer.class, buffer) .call(); From 1dfa3d3d704e23366d3263a390484b04eb71bc3f Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 07:54:10 +0700 Subject: [PATCH 10/46] update EzySecureSocketDataReceiver --- .../nio/socket/EzySecureSocketDataReceiver.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 3bf523bf..c236a493 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -27,11 +27,12 @@ protected byte[] readTcpBytesFromBuffer( EzyNioSecureSocketChannel secureChannel = (EzyNioSecureSocketChannel) channel; SSLEngine engine = secureChannel.getEngine(); + ByteBuffer appBuffer = buffer; int index = Math.abs(channel.hashCode() % threadPoolSize); ByteBuffer tcpNetBuffer = tcpNetBuffers[index]; - while (buffer.hasRemaining()) { + while (appBuffer.hasRemaining()) { tcpNetBuffer.clear(); - SSLEngineResult result = engine.unwrap(buffer, tcpNetBuffer); + SSLEngineResult result = engine.unwrap(appBuffer, tcpNetBuffer); switch (result.getStatus()) { case OK: tcpNetBuffer.flip(); @@ -39,7 +40,7 @@ protected byte[] readTcpBytesFromBuffer( tcpNetBuffer.get(binary); return binary; case BUFFER_OVERFLOW: - tcpNetBuffer = enlargeApplicationBuffer(engine, tcpNetBuffer); + appBuffer = enlargeApplicationBuffer(engine, appBuffer); break; case BUFFER_UNDERFLOW: tcpNetBuffer = handleBufferUnderflow(engine, tcpNetBuffer); From 539cfa17ba91ec61d8ed5891affe5761081b44ac Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 09:36:40 +0700 Subject: [PATCH 11/46] update ssl-keystore --- .../settings/ssl/ssl-keystore.txt | Bin 2054 -> 2057 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ezyfox-server-core/settings/ssl/ssl-keystore.txt b/ezyfox-server-core/settings/ssl/ssl-keystore.txt index 9cd77b53747c699a4353aef62a241cbc79f7fb4c..2535453ebdc4965e37c97f56c02b19eff78f17b1 100644 GIT binary patch delta 1946 zcmV;L2W9w%5Qz|wBY#a9PZ0nB1p+XF1pY7%1_~<%0R#am0uccL1pows1nOcoQt4Uj zk++It@64v~SlYqhsToK1*)8LX$vpxb&OE zPDT8-iK|h1zQ2hPB@?mcgX+S_cg`9T7Xs0Dg(Au+EQ}CZXMY!87q2=wv}&#`J>4+T7n_jWj|>PU{p5oe3QscmK^qkGY^ zdI#qP3vfwm6dZP@kK!8TAwlq8D3^?<@1sqZV=W@}W7bj{t{)kEHmicXXU4Tu(sfB> z@R!+zLLc2f-GACNx|0~~QIqZlAE8Ster>LYt!RMK-(w(@)ni;M9_z5n!-$xAYf;gb z;=P3ho}k1n>|<0= z(CmDl`BWFArwsMQQ4zbG5x9$TUt+}`)!aiWEW&Gda)0LBpCOr1lH=i381ImbL4r7E zupjcqb=Eo?l$$2@sgd4)PQHv^yP&nb;bDl$Rz8ofsPE|e^8enz9xY+3ggmM28@DD6KMd=g+~$j)*$+G=W`RlR*t zw@Te;N%NsheHeKhyIX`{^0f)^$6pZd{GQif)_;;}gvq&*NYc!^;*ryL*XzOuz+X}d ze8&zs)6(PXG1H?BJ_k54Eri!-NCk?Hj&ZbjC9NET;+t$5p+7dq9qMilht5GmcFqMY zQETy0srFC(QYXKOv{3@w+En{e@w^r1A83cW2F_y6f9*n;wjF$?+-lCI^OwBH5sR9; zk$;>VUGOT=fTq}-d2B*j1_3BVtcA_=a_RGNpAWYOx5vuU(_Y*#2Gn}s8O!c+RThU zh5=~CTj0K>|B1N+U2XQx5c2b{18@~eD9L3mP_UGq{^P>48lD96FdE)aR)rZx-?|8wNK>i>WG1q)S2dTrSy)Z7f)iMH^)^bPt^{5y-t)* zUy1}Sya_}2zoYy&AtTywyi3-(kO%vplbO*x%6r>Eobd6&?K0phCQSwe$vTlBnET7o zJPRwjK>#xW#q^*+3~~Sf00966SS~d%IRF3xxiEqPwJ?GKouC5(0RjRD5rV6Y0Y0Z2 zlTrmO9v~MDGBYqYF*7hSGcz?bS{M&AFfuYRF)=eRGBY!in*~09&zm652}>MFO(;vO zT0C`nxL4o*^&VuWZOFX`yhT5TGjP#hDRx(LWPe!A0ce$c75t**FBwqn^2QEl(zjJCn0cI}(il0T8rW2eD+5 z*C}YP(P{(QAyUbIN@&1Zh|UH#=g2Byv`xBG^t6^s_zka9|n4J<JkBD zADOl5#y&@!Eor0T%zbjJiR{oS6;^(8jEn+9y4#SHUmrhhl%zrn%*;WUF_>dKpw3wL zsV>{FoiT%nPO#ng!7|lW5amzAgI@752~$Npe(^2|;$s(oTnhpN0RRCo4F(A+hDe6@ z4FLfQ1potr0RaG(bzI@QSivZXg&C5P^%xJdZSy+$i5Xrb;OR6b2{qMUjQUfrnlkOJ zP^ua8#4ia=e1Z+iYw!-T0+UcERu8A}ieBgCaB;od6t)JSm6HFq#(77$LoJXDDx`Yy zwC*K6M&u%Y1yN+!(n?12KI+s$O1Q73H4Wf0jWu5%VE5Vg;?Cop^8ggk>6@Z@-U02= zb8o1(3ee=Wf$)r#D<)D>(O gETGq&`gZn&S7Kr9DrYZ-vOO%u+eu_$XkEyZqeaT4bpQYW delta 1923 zcmV-}2YmR65QY$tBY#5TV6^}M1pzRE1pP1$1_~<%0R#am0uccL1pows1nEjVr51RL zs;o9~ui>99AfY_lPE!X@r))y{j6#q-OxI|b zn^vgD+a<*{q>R6Jf0zR-)}UG-j6fEx1)8uV1#=Fha7^{rLilM z)2BTJA}?-sHjl#07-u(n2<1z;{~h7VqH!;zwd&h)QjAr3r@uy`)o*C$9l+WJoMWm$ zhXmtwgcYYpEq??Jh!+3NlYgL>n@W>m;7qMf6SrDGtufXf?KqZ|AC6TK{)xYnLVv4? zR647M-qv??AYNj5^OEZf?cvZPI((@0VoZ1ylBg~`oxSeR(&;E@V+?S_2qIJeZv|D{ z5mCAF_|UE5)2h)<*D`o{y-djtcQSPckWJy$RPuLS@PFhVogbb*sF+0!(VVN4ypA&+ zeD^ivmLsb{HOp*ae&8{WxyIma)q#q`Iv3_kJgaVu zzLJ+E4}br#p$0DzMoeBY=4?pA9ISNp?vufWz~B`*1Ip+O+&#MmQSGGJA+8sIi%qR)EU0PVlwSXKo`K^m&g4d%m7tPjY=7;9& zTX3E7c}y!GQG#zm(%hFT7f97T3b-ws+NcS6_Ud&5t7i06+(7}pox-50@Sd$l)hO3}R^uJD(Vxn*Dai;!ILPDA_=@=(eID2YG3c;YBVP-f$8C~MT*{7r zaLK5%KYKIbts?T?M<^v|8Q`|J^v%h8SpsUZgtm0i#dq_FSWxrDNy_UC;88@I!8DKO zbeM0T+M-Gxo1d#v{LGmN-!6dGC&kz5RDYET9_lkhJsRHTxLjL8>!s8#Z@Y4U*f1vG zgz^`>8a#W$mbAhi^LJJb2lsffxxBViH!hkV39F=NU*8%u=&31e_6K97O8NOs3!z_b zY=F1B@`A-aDnkTiiuns70(0_?< z?GM*)r(Y9JB9 z8ohSyrVKy_?ll$rQ7%e%Ehi}km+QD(?Gt%L7Wv$>L~(?7msP^WuMT~_?4hbBz5}QX z!ssCzR9@_0wI1MG*%HZHT@Cna?@AtnI3*#h>87*&abD*)XBm zzxhx)HBZF8VurS@81rvywZ*QJk#4>K-s)Df;TKs7dZAoDp>Q7;~QaQ%BWA!Wu zVA85K8V^M@IBH_|Jpcdz0RRP9E;TSY0008FFoFWJFoFS_paTK{0s;tGl=cD^|H3ho zQw1#?9v2NVGcY$XF)%VUH#Ia`7Y#ErFgGwbFfuhalb!`Wf15CZ!LcdIOXXt}U)0LU zi|*d$^b#TDa=sjeTjV+CM~cFpDX_H=v7u+_>^j$gkEX|1PXOQPx!U6^pq2>`)430q-zH$73ouW#`8crT-z;jxwA~q8Z-x9l$fLu0*%BkFW+d#pQdCXb;VM=G^!*%uoRW&!<#VV|9hPaJmbIc}-dLQ2 zhtG$)Mb2!Lcq<=0P$+$jVFq0R&qr9fUPVYPW~70#0^uK%+y)tcWc{biGWMP}_s+sI z5Nqo3`HYar?uu@Pg|z*SzM`^N+lsZfloHs{5LsiUVA4AHR@Mc9sa^Et@PM$wouWZ( zS&hY?D~^V@YXUPX*CN6kAhP^{#h@MKfSu6^QN?`Nr{Y5e%;o-#w-Q39?IbrMz%Cti z2%=gRd+&D|YZ;b*0A|g7{umQd1?^Wrc3f?7e}w8bAxRjg$=MDb7tk5i-S=hH@ote) zXulBG_K)`FDQ@uzlC<6{M7WZ;R^msdevn=9ji!_t%a?7qpXe-Tg#@ut=gLd-6xp=X z*-e!W2R$>n;N0JKHZ#$1oBv#lZkTAQooC(4%N9cI%RUnpSYuGS5rM15!AlBKuQ@I> JdX-K1Y3rletBU{t From 7f87e67a77f3a058004188de0affad3f6c022bce Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 16:32:34 +0700 Subject: [PATCH 12/46] close connection --- .../api/EzySocketResponseApi.java | 13 +++++++--- .../ezyfoxserver/socket/EzySocketWriter.java | 4 ++-- .../nio/socket/EzyNioSecureSocketChannel.java | 6 +++-- .../nio/socket/EzyNioSocketReader.java | 2 +- .../socket/EzySecureSocketDataReceiver.java | 6 +++-- .../nio/socket/EzySocketDataReceiver.java | 24 +++++++++---------- 6 files changed, 33 insertions(+), 22 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java index b92060bb..47007e0f 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java @@ -7,6 +7,7 @@ import com.tvd12.ezyfox.entity.EzyArray; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.entity.EzySession; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.socket.EzyChannel; public class EzySocketResponseApi extends EzyAbstractResponseApi { @@ -50,9 +51,15 @@ protected Object packMessage( Object message ) throws Exception { EzyChannel channel = session.getChannel(); - return channel == null - ? message - : channel.pack((byte[]) message); + if (channel == null) { + return message; + } + try { + return channel.pack((byte[]) message); + } catch (EzyConnectionCloseException e) { + session.disconnect(); + throw e; + } } @Override diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java index 2a49da1a..21df4219 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java @@ -30,8 +30,8 @@ private void doProcessSessionTicketsQueue() { processSessionQueue(session); } catch (InterruptedException e) { logger.info("socket-writer thread interrupted"); - } catch (Throwable throwable) { - logger.info("problems in socket-writer, thread", throwable); + } catch (Throwable e) { + logger.info("problems in socket-writer, thread", e); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 91b46fbc..1049967b 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -1,5 +1,6 @@ package com.tvd12.ezyfoxserver.nio.socket; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import lombok.Getter; import javax.net.ssl.SSLEngine; @@ -48,8 +49,9 @@ public byte[] pack(byte[] bytes) throws Exception { throw new IOException("Buffer underflow occurred after a wrap"); case CLOSED: engine.closeOutbound(); - channel.close(); - break; + throw new EzyConnectionCloseException( + "ssl wrap result status is CLOSE" + ); default: throw new IOException("Invalid SSL status: " + result.getStatus()); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java index c809cc19..5da66655 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java @@ -32,7 +32,7 @@ public void handleEvent() { doProcessReadyKeys(); Thread.sleep(3L); } catch (Throwable e) { - logger.info("I/O error at socket-reader: {}({})", e.getClass().getName(), e.getMessage()); + logger.info("I/O error at socket-reader", e); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index c236a493..2b5eb077 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -1,5 +1,6 @@ package com.tvd12.ezyfoxserver.nio.socket; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.socket.EzyChannel; import javax.net.ssl.SSLEngine; @@ -47,8 +48,9 @@ protected byte[] readTcpBytesFromBuffer( break; case CLOSED: engine.closeOutbound(); - channel.close(); - throw new IOException("connection close"); + throw new EzyConnectionCloseException( + "ssl unwrap result status is CLOSE" + ); default: throw new IOException( "Invalid SSL status: " + result.getStatus() diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java index 08d522a0..f1b7bd97 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java @@ -6,6 +6,7 @@ import com.tvd12.ezyfox.util.EzyDestroyable; import com.tvd12.ezyfox.util.EzyLoggable; import com.tvd12.ezyfoxserver.constant.EzyCoreConstants; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.nio.handler.EzyHandlerGroup; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.nio.websocket.EzyWsHandlerGroup; @@ -61,11 +62,13 @@ private void doTcpReceive(SocketChannel channel, ByteBuffer buffer) { try { tcpReadBytes(channel, buffer); } catch (Throwable e) { + if (e instanceof EzyConnectionCloseException) { + tcpCloseConnection(channel); + } logger.info( - "I/O error at tcp-data-reader (channel: {}): {}({})", + "I/O error at tcp-data-reader (channel: {})", channel, - e.getClass().getName(), - e.getMessage() + e ); } } @@ -143,10 +146,9 @@ private void doUdpReceive(Object channel, EzyMessage message) { } } catch (Throwable e) { logger.info( - "I/O error at udp-message-received (channel: {}): {}({})", + "I/O error at udp-message-received (channel: {})", channel, - e.getClass().getName(), - e.getMessage() + e ); } @@ -170,10 +172,9 @@ private void doWsReceive(Session session, String message) { } } catch (Throwable e) { logger.info( - "I/O error at ws-message-received (session: {}): {}({})", + "I/O error at ws-message-received (session: {})", session, - e.getClass().getName(), - e.getMessage() + e ); } } @@ -186,10 +187,9 @@ private void doWsReceive(Session session, byte[] payload, int offset, int len) { } } catch (Throwable e) { logger.info( - "I/O error at ws-message-received (session: {}): {}({})", + "I/O error at ws-message-received (session: {})", session, - e.getClass().getName(), - e.getMessage() + e ); } } From 3161995be53e8964a8cd97b327bb07dd2b0eb5d9 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 21:18:20 +0700 Subject: [PATCH 13/46] EzySslHandshakeHandlerTest unitest for unwrap --- .../EzyConnectionCloseException.java | 10 + .../ssl/EzySslHandshakeHandler.java | 44 +- .../ezyfoxserver/ssl/SslByteBuffers.java | 38 +- .../testing/ssl/EzySSLContextSpiForTest.java | 10 + .../ssl/EzySslHandshakeHandlerTest.java | 471 ++++++++++++++++++ .../nio/socket/EzyNioSecureSocketChannel.java | 10 +- .../socket/EzySecureSocketDataReceiver.java | 25 +- 7 files changed, 533 insertions(+), 75 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java new file mode 100644 index 00000000..f5fd3c0c --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java @@ -0,0 +1,10 @@ +package com.tvd12.ezyfoxserver.exception; + +import java.io.IOException; + +public class EzyConnectionCloseException extends IOException { + + public EzyConnectionCloseException(String message) { + super(message); + } +} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index 0dc60b3b..b1dc78f7 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -8,8 +8,8 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeApplicationBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargePacketBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; @@ -54,7 +54,6 @@ public SSLEngine handle( try { engine.closeInbound(); } catch (SSLException e) { - sslException = e; logger.info( "This engine was forced to close inbound, " + "without having received the proper SSL/TLS close " + @@ -71,24 +70,21 @@ public SSLEngine handle( peerNetData.compact(); handshakeStatus = result.getHandshakeStatus(); } catch (SSLException e) { - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - sslException = e; - logger.error( + logger.info( "A problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + "Will try to properly close connection..." ); + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); break; } switch (result.getStatus()) { - case OK: - break; case BUFFER_OVERFLOW: - peerAppData = enlargeApplicationBuffer(engine, peerAppData); + peerAppData = enlargeBuffer(peerAppData, appBufferSize); break; case BUFFER_UNDERFLOW: - peerNetData = handleBufferUnderflow(engine, peerNetData); + peerNetData = enlargeBufferIfNeed(peerNetData, packetBufferSize); break; case CLOSED: if (engine.isOutboundDone()) { @@ -98,10 +94,8 @@ public SSLEngine handle( handshakeStatus = engine.getHandshakeStatus(); break; } - default: - throw new SSLException( - "Invalid SSL status: " + result.getStatus() - ); + default: // OK + break; } break; case NEED_WRAP: @@ -113,7 +107,7 @@ public SSLEngine handle( engine.closeOutbound(); handshakeStatus = engine.getHandshakeStatus(); sslException = e; - logger.error( + logger.info( "A problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + "Will try to properly close connection..." @@ -131,7 +125,7 @@ public SSLEngine handle( } break; case BUFFER_OVERFLOW: - netBuffer = enlargePacketBuffer(engine, netBuffer); + netBuffer = enlargeBuffer(netBuffer, packetBufferSize); break; case BUFFER_UNDERFLOW: throw new SSLException("Buffer underflow occurred after a wrap."); @@ -146,7 +140,7 @@ public SSLEngine handle( } peerNetData.clear(); } catch (Exception e) { - logger.error( + logger.info( "Failed to send server's close message " + "due to socket channel's failure." ); @@ -179,18 +173,4 @@ public SSLEngine handle( } return engine; } - - protected ByteBuffer handleBufferUnderflow( - SSLEngine engine, - ByteBuffer buffer - ) { - if (engine.getSession().getPacketBufferSize() < buffer.limit()) { - return buffer; - } else { - ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer); - buffer.flip(); - replaceBuffer.put(buffer); - return replaceBuffer; - } - } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java index 4cf8164e..fd9a5ec6 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java @@ -1,32 +1,11 @@ package com.tvd12.ezyfoxserver.ssl; -import javax.net.ssl.SSLEngine; import java.nio.ByteBuffer; public final class SslByteBuffers { private SslByteBuffers() {} - public static ByteBuffer enlargePacketBuffer( - SSLEngine engine, - ByteBuffer buffer - ) { - return enlargeBuffer( - buffer, - engine.getSession().getPacketBufferSize() - ); - } - - public static ByteBuffer enlargeApplicationBuffer( - SSLEngine engine, - ByteBuffer buffer - ) { - return enlargeBuffer( - buffer, - engine.getSession().getApplicationBufferSize() - ); - } - public static ByteBuffer enlargeBuffer( ByteBuffer buffer, int sessionProposedCapacity @@ -35,4 +14,21 @@ public static ByteBuffer enlargeBuffer( ? ByteBuffer.allocate(sessionProposedCapacity) : ByteBuffer.allocate(buffer.capacity() * 2); } + + public static ByteBuffer enlargeBufferIfNeed( + ByteBuffer buffer, + int packageBufferSize + ) { + if (packageBufferSize < buffer.limit()) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargeBuffer( + buffer, + packageBufferSize + ); + buffer.flip(); + replaceBuffer.put(buffer); + return replaceBuffer; + } + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java new file mode 100644 index 00000000..b481c9db --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java @@ -0,0 +1,10 @@ +package com.tvd12.ezyfoxserver.testing.ssl; + +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLEngine; + +public abstract class EzySSLContextSpiForTest extends SSLContextSpi { + + @Override + public abstract SSLEngine engineCreateSSLEngine(); +} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java new file mode 100644 index 00000000..600a9fc2 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java @@ -0,0 +1,471 @@ +package com.tvd12.ezyfoxserver.testing.ssl; + +import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.FieldUtil; +import com.tvd12.test.util.RandomUtil; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.*; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.mockito.Mockito.*; + +public class EzySslHandshakeHandlerTest { + + private SSLContext sslContext; + private EzySSLContextSpiForTest sslContextSpi; + private SSLEngine sslEngine; + private SSLSession sslSession; + private SocketChannel socketChannel; + private final int timeout = 50; + private final int appBufferSize = 128; + private final int packetBufferSize = 128; + private EzySslHandshakeHandler instance; + + @BeforeMethod + public void setup() { + this.sslContext = mock(SSLContext.class); + this.sslContextSpi = mock(EzySSLContextSpiForTest.class); + this.sslEngine = mock(SSLEngine.class); + this.sslSession = mock(SSLSession.class); + this.socketChannel = mock(SocketChannel.class); + this.instance = new EzySslHandshakeHandler( + sslContext, + timeout + ); + FieldUtil.setFieldValue( + sslContext, + "contextSpi", + sslContextSpi + ); + when(sslContextSpi.engineCreateSSLEngine()).thenReturn(sslEngine); + when(sslEngine.getSession()).thenReturn(sslSession); + when(sslSession.getApplicationBufferSize()).thenReturn(appBufferSize); + when(sslSession.getPacketBufferSize()).thenReturn(packetBufferSize); + } + + @AfterMethod + public void verifyAll() throws Exception { + verifyNoMoreInteractions(sslContext); + + verify(sslContextSpi, times(1)).engineCreateSSLEngine(); + verifyNoMoreInteractions(sslContextSpi); + + verify(sslEngine, times(1)).beginHandshake(); + verify(sslEngine, times(1)).setUseClientMode(false); + verify(sslEngine, times(1)).getSession(); + verifyNoMoreInteractions(sslEngine); + + verify(sslSession, times(1)).getPacketBufferSize(); + verify(sslSession, times(1)).getApplicationBufferSize(); + verifyNoMoreInteractions(sslSession); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsOk() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_UNWRAP + ); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenReturn(engineResult); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).closeInbound(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0CloseInboundError() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + + SSLException error = new SSLException("test"); + doThrow(error).when(sslEngine).closeInbound(); + + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).closeInbound(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0OutboundNotDone() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + + when(sslEngine.isInboundDone()).thenReturn(true); + + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).isOutboundDone(); + verify(sslEngine, times(1)).closeInbound(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0InOutboundDone() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + + when(sslEngine.isInboundDone()).thenReturn(true); + when(sslEngine.isOutboundDone()).thenReturn(true); + + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handle(socketChannel) + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).isOutboundDone(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineUnwrapThrowException() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLException error = new SSLException("test"); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenThrow(error); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferOverflow() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NEED_UNWRAP, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(2)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferUnderFlow() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NEED_UNWRAP, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(2)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosed() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).isOutboundDone(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosedOutboundDone() throws Exception { + // given + when(sslEngine.isOutboundDone()).thenReturn(true); + + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handle(socketChannel) + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).isOutboundDone(); + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 1049967b..8a6ac5d0 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -5,11 +5,12 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargePacketBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { @@ -27,9 +28,8 @@ public EzyNioSecureSocketChannel( @Override public byte[] pack(byte[] bytes) throws Exception { ByteBuffer buffer = ByteBuffer.wrap(bytes); - int netBufferLength = engine - .getSession() - .getPacketBufferSize(); + SSLSession session = engine.getSession(); + int netBufferLength = session.getPacketBufferSize(); ByteBuffer netBuffer = ByteBuffer.allocate(netBufferLength); while (buffer.hasRemaining()) { SSLEngineResult result = engine.wrap( @@ -43,7 +43,7 @@ public byte[] pack(byte[] bytes) throws Exception { netBuffer.get(answer); return answer; case BUFFER_OVERFLOW: - netBuffer = enlargePacketBuffer(engine, netBuffer); + netBuffer = enlargeBuffer(netBuffer, netBufferLength); break; case BUFFER_UNDERFLOW: throw new IOException("Buffer underflow occurred after a wrap"); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 2b5eb077..750e106a 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -5,11 +5,12 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; import java.io.IOException; import java.nio.ByteBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeApplicationBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargePacketBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { @@ -28,6 +29,9 @@ protected byte[] readTcpBytesFromBuffer( EzyNioSecureSocketChannel secureChannel = (EzyNioSecureSocketChannel) channel; SSLEngine engine = secureChannel.getEngine(); + SSLSession session = engine.getSession(); + int appBufferSize = session.getApplicationBufferSize(); + int packageBufferSize = session.getPacketBufferSize(); ByteBuffer appBuffer = buffer; int index = Math.abs(channel.hashCode() % threadPoolSize); ByteBuffer tcpNetBuffer = tcpNetBuffers[index]; @@ -41,10 +45,10 @@ protected byte[] readTcpBytesFromBuffer( tcpNetBuffer.get(binary); return binary; case BUFFER_OVERFLOW: - appBuffer = enlargeApplicationBuffer(engine, appBuffer); + appBuffer = enlargeBuffer(appBuffer, appBufferSize); break; case BUFFER_UNDERFLOW: - tcpNetBuffer = handleBufferUnderflow(engine, tcpNetBuffer); + tcpNetBuffer = enlargeBufferIfNeed(tcpNetBuffer, packageBufferSize); break; case CLOSED: engine.closeOutbound(); @@ -60,19 +64,6 @@ protected byte[] readTcpBytesFromBuffer( return new byte[0]; } - protected ByteBuffer handleBufferUnderflow( - SSLEngine engine, - ByteBuffer buffer) { - if (engine.getSession().getPacketBufferSize() < buffer.limit()) { - return buffer; - } else { - ByteBuffer replaceBuffer = enlargePacketBuffer(engine, buffer); - buffer.flip(); - replaceBuffer.put(buffer); - return replaceBuffer; - } - } - public static Builder builder() { return new Builder(); } From 1c1126466ab9b2cc003c8f2f2bb504d9a2d0c029 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 22:34:11 +0700 Subject: [PATCH 14/46] done EzySslHandshakeHandlerTest --- .../ssl/EzySslHandshakeHandler.java | 41 +- .../ssl/EzySslHandshakeHandlerTest.java | 361 +++++++++++++++++- 2 files changed, 369 insertions(+), 33 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index b1dc78f7..3f832fab 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -36,11 +36,14 @@ public SSLEngine handle( ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize); SSLEngineResult result; - SSLException sslException = null; SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); long currentTime = System.currentTimeMillis(); long endTime = currentTime + timeout; while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { + currentTime = System.currentTimeMillis(); + if (currentTime >= endTime) { + throw new SSLException("Timeout"); + } switch (handshakeStatus) { case NEED_UNWRAP: int readBytes = socketChannel.read(peerNetData); @@ -106,7 +109,6 @@ public SSLEngine handle( } catch (SSLException e) { engine.closeOutbound(); handshakeStatus = engine.getHandshakeStatus(); - sslException = e; logger.info( "A problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + @@ -115,15 +117,6 @@ public SSLEngine handle( break; } switch (result.getStatus()) { - case OK : - netBuffer.flip(); - while (netBuffer.hasRemaining()) { - int writeBytes = socketChannel.write(netBuffer); - if (writeBytes < 0) { - throw new SSLException("Maybe client closed."); - } - } - break; case BUFFER_OVERFLOW: netBuffer = enlargeBuffer(netBuffer, packetBufferSize); break; @@ -133,10 +126,7 @@ public SSLEngine handle( try { netBuffer.flip(); while (netBuffer.hasRemaining()) { - int writeBytes = socketChannel.write(netBuffer); - if (writeBytes < 0) { - throw new SSLException("Maybe client closed."); - } + socketChannel.write(netBuffer); } peerNetData.clear(); } catch (Exception e) { @@ -147,29 +137,22 @@ public SSLEngine handle( handshakeStatus = engine.getHandshakeStatus(); } break; - default: - throw new SSLException( - "Invalid SSL status: " + result.getStatus() - ); + default: // OK + netBuffer.flip(); + while (netBuffer.hasRemaining()) { + socketChannel.write(netBuffer); + } + break; } break; - case NEED_TASK: + default: // NEED_TASK: Runnable task; while ((task = engine.getDelegatedTask()) != null) { task.run(); } handshakeStatus = engine.getHandshakeStatus(); break; - default: - throw new SSLException("Invalid SSL status: " + handshakeStatus); } - currentTime = System.currentTimeMillis(); - if (currentTime >= endTime) { - throw new SSLException("Timeout"); - } - } - if (sslException != null) { - throw sslException; } return engine; } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java index 600a9fc2..1808b7c9 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java @@ -1,5 +1,7 @@ package com.tvd12.ezyfoxserver.testing.ssl; +import com.tvd12.ezyfox.io.EzyByteBuffers; +import com.tvd12.ezyfox.util.EzyThreads; import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import com.tvd12.test.assertion.Asserts; import com.tvd12.test.reflect.FieldUtil; @@ -9,6 +11,7 @@ import org.testng.annotations.Test; import javax.net.ssl.*; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.concurrent.atomic.AtomicInteger; @@ -23,8 +26,7 @@ public class EzySslHandshakeHandlerTest { private SSLSession sslSession; private SocketChannel socketChannel; private final int timeout = 50; - private final int appBufferSize = 128; - private final int packetBufferSize = 128; + private final int bufferSize = 128; private EzySslHandshakeHandler instance; @BeforeMethod @@ -45,8 +47,8 @@ public void setup() { ); when(sslContextSpi.engineCreateSSLEngine()).thenReturn(sslEngine); when(sslEngine.getSession()).thenReturn(sslSession); - when(sslSession.getApplicationBufferSize()).thenReturn(appBufferSize); - when(sslSession.getPacketBufferSize()).thenReturn(packetBufferSize); + when(sslSession.getApplicationBufferSize()).thenReturn(bufferSize); + when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); } @AfterMethod @@ -468,4 +470,355 @@ public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosedOutboundDon .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); verify(sslEngine, times(1)).isOutboundDone(); } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsOk() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineThrowException() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + SSLException error = new SSLException("test"); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenThrow(error); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsBufferOverflow() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NEED_WRAP, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger wrapCallCount = new AtomicInteger(); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = wrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultBufferOverflow; + } + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResultOk; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(2)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsBufferUnderflow() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenReturn(engineResult); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handle(socketChannel) + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosed() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteThrowException() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + IOException error = new IOException("test"); + when(socketChannel.write(any(ByteBuffer.class))).thenThrow(error); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedTask() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_TASK; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + Runnable task = mock(Runnable.class); + AtomicInteger getDelegatedTaskCallCount = new AtomicInteger(); + when(sslEngine.getDelegatedTask()).thenAnswer(it -> { + int callCount = getDelegatedTaskCallCount.incrementAndGet(); + if (callCount == 1) { + return task; + } + return null; + }); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(2)).getDelegatedTask(); + + verify(task, times(1)).run(); + verifyNoMoreInteractions(task); + } + + @Test + public void handleCaseHandshakeStatusIsFinish() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.FINISHED + ); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(sslEngine, times(1)).getHandshakeStatus(); + } + + @Test + public void handleCaseHandshakeStatusIsNotHandshake() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING + ); + + // when + SSLEngine actual = instance.handle(socketChannel); + + // then + Asserts.assertEquals(actual, sslEngine); + + verify(sslEngine, times(1)).getHandshakeStatus(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapTimeout() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_UNWRAP + ); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(it -> { + EzyThreads.sleep(timeout * 2); + return readBytes; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NEED_WRAP, + 0, + 0 + ); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenReturn(engineResult); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handle(socketChannel) + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } } From 083104b7d2c794acbc3d3b6c676976bd1230cc8a Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 22:50:35 +0700 Subject: [PATCH 15/46] update EzyAbstractResponseApiTest --- .../api/EzyAbstractResponseApiTest.java | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java index 9f2c1240..d8822f23 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java @@ -5,7 +5,9 @@ import com.tvd12.ezyfox.factory.EzyEntityFactory; import com.tvd12.ezyfoxserver.api.EzyAbstractResponseApi; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; +import com.tvd12.ezyfoxserver.entity.EzySession; import com.tvd12.ezyfoxserver.response.EzyPackage; +import com.tvd12.ezyfoxserver.socket.EzyPacket; import com.tvd12.test.assertion.Asserts; import com.tvd12.test.reflect.MethodInvoker; import com.tvd12.test.reflect.MethodUtil; @@ -73,6 +75,132 @@ public void encryptMessageContentTest() { Asserts.assertEquals(UnsupportedOperationException.class, e.getCause().getCause().getClass()); } + @Test + public void normalResponseImmediateSendException() throws Exception { + // given + InternalResponseApi sut = new InternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + + EzySession session = mock(EzySession.class); + RuntimeException error = new RuntimeException("test"); + doThrow(error).when(session).sendNow(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, true); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).sendNow(any(EzyPacket.class)); + verifyNoMoreInteractions(session); + } + + @Test + public void normalResponseSendException() throws Exception { + // given + InternalResponseApi sut = new InternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + + EzySession session = mock(EzySession.class); + RuntimeException error = new RuntimeException("test"); + doThrow(error).when(session).send(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, false); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).send(any(EzyPacket.class)); + verifyNoMoreInteractions(session); + } + + @Test + public void secureResponseImmediateSendException() throws Exception { + // given + InternalResponseApi2 sut = new InternalResponseApi2(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(true); + + EzySession session = mock(EzySession.class); + RuntimeException error = new RuntimeException("test"); + doThrow(error).when(session).sendNow(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, true); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).sendNow(any(EzyPacket.class)); + verify(session, times(1)).getSessionKey(); + verifyNoMoreInteractions(session); + } + + @Test + public void secureResponseSendException() throws Exception { + // given + InternalResponseApi2 sut = new InternalResponseApi2(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(true); + + EzySession session = mock(EzySession.class); + RuntimeException error = new RuntimeException("test"); + doThrow(error).when(session).send(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, false); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).send(any(EzyPacket.class)); + verify(session, times(1)).getSessionKey(); + verifyNoMoreInteractions(session); + } + private static class InternalResponseApi extends EzyAbstractResponseApi { @Override @@ -85,4 +213,27 @@ protected Object encodeData(EzyArray data) { return null; } } + + private static class InternalResponseApi2 extends EzyAbstractResponseApi { + + @Override + protected EzyConstant getConnectionType() { + return EzyConnectionType.SOCKET; + } + + @Override + protected Object encodeData(EzyArray data) { + return null; + } + + @Override + protected byte[] dataToMessageContent(EzyArray data) throws Exception { + return new byte[0]; + } + + @Override + protected byte[] encryptMessageContent(byte[] messageContent, byte[] encryptionKey) throws Exception { + return messageContent; + } + } } From 0e8df3c8464e3aa85c35cb3b562265c0f463dc86 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 13 Jul 2023 22:59:41 +0700 Subject: [PATCH 16/46] add setting unit test --- .../setting/EzySimpleSocketSettingTest.java | 74 +++++++++++++++++++ .../setting/EzySocketSettingBuilderTest.java | 45 +++++++++++ 2 files changed, 119 insertions(+) create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java index 323b6590..3e8ae0b1 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java @@ -1,6 +1,8 @@ package com.tvd12.ezyfoxserver.testing.setting; +import com.tvd12.ezyfoxserver.constant.SslType; import com.tvd12.ezyfoxserver.setting.EzySimpleSocketSetting; +import com.tvd12.test.assertion.Asserts; import com.tvd12.test.base.BaseTest; import org.testng.annotations.Test; @@ -20,4 +22,76 @@ public void test() { setting.setTcpNoDelay(true); assert setting.isTcpNoDelay(); } + + @Test + public void isL4SslActiveTrueTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.L4); + + // when + // then + Asserts.assertTrue(setting.isL4SslActive()); + } + + @Test + public void isL4SslActiveFalseDueToSslEnableTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(false); + setting.setSslType(SslType.L4); + + // when + // then + Asserts.assertFalse(setting.isL4SslActive()); + } + + @Test + public void isL4SslActiveFalseDueToSslTypeTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.L7); + + // when + // then + Asserts.assertFalse(setting.isL4SslActive()); + } + + @Test + public void isL7SslActiveTrueTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.L7); + + // when + // then + Asserts.assertTrue(setting.isL7SslActive()); + } + + @Test + public void isL7SslActiveFalseDueToSslEnableTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(false); + setting.setSslType(SslType.L7); + + // when + // then + Asserts.assertFalse(setting.isL7SslActive()); + } + + @Test + public void isL7SslActiveFalseDueToSslTypeTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.L4); + + // when + // then + Asserts.assertFalse(setting.isL7SslActive()); + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java new file mode 100644 index 00000000..90de4083 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java @@ -0,0 +1,45 @@ +package com.tvd12.ezyfoxserver.testing.setting; + +import com.tvd12.ezyfoxserver.constant.SslType; +import com.tvd12.ezyfoxserver.setting.EzySocketSetting; +import com.tvd12.ezyfoxserver.setting.EzySocketSettingBuilder; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.util.RandomUtil; +import org.testng.annotations.Test; + +public class EzySocketSettingBuilderTest { + + @Test + public void test() { + // given + SslType sslType = RandomUtil.randomEnumValue( + SslType.class + ); + int sslHandshakeTimeout = RandomUtil.randomInt(); + int connectionAcceptorThreadPoolSize = RandomUtil.randomInt(); + int sslConnectionAcceptorThreadPoolSize = RandomUtil.randomInt(); + + // when + EzySocketSetting setting = new EzySocketSettingBuilder() + .sslType(sslType) + .sslHandshakeTimeout(sslHandshakeTimeout) + .connectionAcceptorThreadPoolSize(connectionAcceptorThreadPoolSize) + .sslConnectionAcceptorThreadPoolSize(sslConnectionAcceptorThreadPoolSize) + .build(); + + // then + Asserts.assertEquals(setting.getSslType(), sslType); + Asserts.assertEquals( + setting.getSslHandshakeTimeout(), + sslHandshakeTimeout + ); + Asserts.assertEquals( + setting.getConnectionAcceptorThreadPoolSize(), + connectionAcceptorThreadPoolSize + ); + Asserts.assertEquals( + setting.getSslConnectionAcceptorThreadPoolSize(), + sslConnectionAcceptorThreadPoolSize + ); + } +} From 8608b36881e6eba056a3af90ab33176fd8526ed7 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 14 Jul 2023 21:00:49 +0700 Subject: [PATCH 17/46] change SslType and update EzyAbstractServerBootstrapBuilderTest --- .../EzyAbstractServerBootstrapBuilder.java | 4 +-- .../tvd12/ezyfoxserver/constant/SslType.java | 4 +-- .../controller/EzyHandshakeController.java | 2 +- .../setting/EzySimpleSocketSetting.java | 2 +- .../setting/EzySocketSetting.java | 8 +++--- .../setting/EzySocketSettingBuilder.java | 2 +- .../src/main/resources/ezy-settings-1.0.0.xsd | 4 +-- .../src/main/resources/ezy-settings.xml | 2 +- ...EzyAbstractServerBootstrapBuilderTest.java | 26 +++++++++++++++++++ .../EzyHandShakeControllerTest.java | 4 +-- .../setting/EzySimpleSocketSettingTest.java | 24 ++++++++--------- .../nio/EzySocketServerBootstrap.java | 2 +- .../EzyNioServerBootstrapBuilderImpl.java | 2 +- 13 files changed, 55 insertions(+), 31 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java index 1f3bc048..231400e2 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java @@ -4,7 +4,6 @@ import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.EzyServerBootstrap; import com.tvd12.ezyfoxserver.config.EzyConfig; -import com.tvd12.ezyfoxserver.constant.SslType; import com.tvd12.ezyfoxserver.context.EzyServerContext; import com.tvd12.ezyfoxserver.setting.*; import com.tvd12.ezyfoxserver.ssl.EzySslContextInitializer; @@ -48,8 +47,7 @@ protected EzyServerContext newServerContext(EzyServer server) { protected SSLContext newSslContext(EzySslConfigSetting sslConfig) { EzySocketSetting socketSetting = getSocketSetting(); EzyWebSocketSetting webSocketSetting = getWebsocketSetting(); - boolean activeSslForSocket = socketSetting.isSslActive() - && socketSetting.getSslType() == SslType.L4; + boolean activeSslForSocket = socketSetting.isCertificationSslActive(); boolean activeSslForWebsocket = webSocketSetting.isSslActive(); if (activeSslForSocket || activeSslForWebsocket) { return newSslContextInitializer(sslConfig).init(); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java index a61778e8..92882baf 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java @@ -1,6 +1,6 @@ package com.tvd12.ezyfoxserver.constant; public enum SslType { - L4, - L7 + CERTIFICATION, + CUSTOMIZATION } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java index a9597fcd..caec1552 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java @@ -43,7 +43,7 @@ protected void handleSocketSSL(EzyServerContext ctx, EzyHandshakeEvent event) { .getServer() .getSettings() .getSocket() - .isL7SslActive(); + .isCustomizationSslActive(); if (!enableL7SSL) { return; } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java index d2df3cd2..b9e2ece8 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java @@ -44,7 +44,7 @@ public class EzySimpleSocketSetting public EzySimpleSocketSetting() { super(); setPort(3005); - setSslType(SslType.L7); + setSslType(SslType.CUSTOMIZATION); setSslHandshakeTimeout(300); setMaxRequestSize(4096); setConnectionAcceptorThreadPoolSize(1); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 8e8cc2c9..9687b880 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java @@ -17,11 +17,11 @@ public interface EzySocketSetting extends EzyBaseSocketSetting { int getWriterThreadPoolSize(); - default boolean isL4SslActive() { - return isSslActive() && getSslType() == SslType.L4; + default boolean isCertificationSslActive() { + return isSslActive() && getSslType() == SslType.CERTIFICATION; } - default boolean isL7SslActive() { - return isSslActive() && getSslType() == SslType.L7; + default boolean isCustomizationSslActive() { + return isSslActive() && getSslType() == SslType.CUSTOMIZATION; } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java index 318752f2..946c8721 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java @@ -17,7 +17,7 @@ public class EzySocketSettingBuilder extends public EzySocketSettingBuilder() { this.port = 3005; - this.sslType = SslType.L7; + this.sslType = SslType.CUSTOMIZATION; this.sslHandshakeTimeout = 300; this.maxRequestSize = 32768; this.connectionAcceptorThreadPoolSize = 1; diff --git a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd index 8660b650..0732b7bb 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd +++ b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd @@ -104,8 +104,8 @@ - - + + diff --git a/ezyfox-server-core/src/main/resources/ezy-settings.xml b/ezyfox-server-core/src/main/resources/ezy-settings.xml index 1481335b..d6f668b0 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-settings.xml @@ -27,7 +27,7 @@
0.0.0.0
true true - L7 + CUSTOMIZATION 300 true 4096 diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/builder/EzyAbstractServerBootstrapBuilderTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/builder/EzyAbstractServerBootstrapBuilderTest.java index 484f5f9b..e01105d3 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/builder/EzyAbstractServerBootstrapBuilderTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/builder/EzyAbstractServerBootstrapBuilderTest.java @@ -1,6 +1,7 @@ package com.tvd12.ezyfoxserver.testing.builder; import com.tvd12.ezyfoxserver.EzySimpleServer; +import com.tvd12.ezyfoxserver.constant.SslType; import com.tvd12.ezyfoxserver.setting.*; import com.tvd12.ezyfoxserver.testing.BaseCoreTest; import com.tvd12.ezyfoxserver.testing.MyTestServerBootstrapBuilder; @@ -87,4 +88,29 @@ public void newSslContextTest() { Asserts.assertNotNull(sslContext); } + + @Test + public void newSslContextCauseBySocketTest() { + // given + EzySimpleServer server = newServer(); + EzySimpleSocketSetting socketSetting = + (EzySimpleSocketSetting) server.getSettings().getSocket(); + socketSetting.setSslActive(true); + socketSetting.setSslType(SslType.CERTIFICATION); + + MyTestServerBootstrapBuilder builder = + (MyTestServerBootstrapBuilder) new MyTestServerBootstrapBuilder() + .server(server); + + EzySimpleSslConfigSetting setting = new EzySimpleSslConfigSetting(); + + // when + SSLContext sslContext = MethodInvoker.create() + .object(builder) + .method("newSslContext") + .param(EzySslConfigSetting.class, setting) + .invoke(SSLContext.class); + + Asserts.assertNotNull(sslContext); + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java index 4794ed0c..3ff322de 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java @@ -72,7 +72,7 @@ public void handleSocketSSLTest() { EzySettings settings = mock(EzySettings.class); EzySocketSetting socketSetting = mock(EzySocketSetting.class); when(settings.getSocket()).thenReturn(socketSetting); - when(socketSetting.isL7SslActive()).thenReturn(true); + when(socketSetting.isCustomizationSslActive()).thenReturn(true); when(serverContext.getServer()).thenReturn(server); when(server.getSettings()).thenReturn(settings); @@ -95,7 +95,7 @@ public void handleSocketSSLTest() { sut.handle(serverContext, request); // then - verify(socketSetting, times(1)).isL7SslActive(); + verify(socketSetting, times(1)).isCustomizationSslActive(); verify(session, times(1)).setClientId(clientId); verify(session, times(1)).setClientKey(clientKey); verify(session, times(1)).setClientType(clientType); diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java index 3e8ae0b1..795630e4 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySimpleSocketSettingTest.java @@ -28,11 +28,11 @@ public void isL4SslActiveTrueTest() { // given EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setSslActive(true); - setting.setSslType(SslType.L4); + setting.setSslType(SslType.CERTIFICATION); // when // then - Asserts.assertTrue(setting.isL4SslActive()); + Asserts.assertTrue(setting.isCertificationSslActive()); } @Test @@ -40,11 +40,11 @@ public void isL4SslActiveFalseDueToSslEnableTest() { // given EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setSslActive(false); - setting.setSslType(SslType.L4); + setting.setSslType(SslType.CERTIFICATION); // when // then - Asserts.assertFalse(setting.isL4SslActive()); + Asserts.assertFalse(setting.isCertificationSslActive()); } @Test @@ -52,11 +52,11 @@ public void isL4SslActiveFalseDueToSslTypeTest() { // given EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setSslActive(true); - setting.setSslType(SslType.L7); + setting.setSslType(SslType.CUSTOMIZATION); // when // then - Asserts.assertFalse(setting.isL4SslActive()); + Asserts.assertFalse(setting.isCertificationSslActive()); } @Test @@ -64,11 +64,11 @@ public void isL7SslActiveTrueTest() { // given EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setSslActive(true); - setting.setSslType(SslType.L7); + setting.setSslType(SslType.CUSTOMIZATION); // when // then - Asserts.assertTrue(setting.isL7SslActive()); + Asserts.assertTrue(setting.isCustomizationSslActive()); } @Test @@ -76,11 +76,11 @@ public void isL7SslActiveFalseDueToSslEnableTest() { // given EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setSslActive(false); - setting.setSslType(SslType.L7); + setting.setSslType(SslType.CUSTOMIZATION); // when // then - Asserts.assertFalse(setting.isL7SslActive()); + Asserts.assertFalse(setting.isCustomizationSslActive()); } @Test @@ -88,10 +88,10 @@ public void isL7SslActiveFalseDueToSslTypeTest() { // given EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setSslActive(true); - setting.setSslType(SslType.L4); + setting.setSslType(SslType.CERTIFICATION); // when // then - Asserts.assertFalse(setting.isL7SslActive()); + Asserts.assertFalse(setting.isCustomizationSslActive()); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index b77eec56..33af8f32 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -148,7 +148,7 @@ private ServerSocketChannel newServerSocketChannel() throws Exception { private boolean isEnableL4Ssl() { EzySocketSetting setting = getSocketSetting(); - return setting.isL4SslActive(); + return setting.isCertificationSslActive(); } public int getSslHandshakeTimeout() { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index c1b19c58..7938a068 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -107,7 +107,7 @@ private ExecutorService newStatsThreadPool() { private EzySocketDataReceiver newSocketDataReceiver(EzyHandlerGroupManager handlerGroupManager) { EzySocketSetting setting = getSocketSetting(); return ( - setting.isL4SslActive() + setting.isCertificationSslActive() ? EzySecureSocketDataReceiver.builder() : EzySocketDataReceiver.builder() ) From f8418536d042e9d3d6c031adbd39f7239006881b Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 14 Jul 2023 21:11:23 +0700 Subject: [PATCH 18/46] update EzySocketResponseApiTest --- .../testing/api/EzySocketResponseApiTest.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java index fe1e53eb..88d567e2 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java @@ -7,7 +7,11 @@ import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.constant.EzyTransportType; import com.tvd12.ezyfoxserver.entity.EzySession; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.ezyfoxserver.socket.EzySimplePackage; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.MethodInvoker; import com.tvd12.test.util.RandomUtil; import org.testng.annotations.Test; @@ -112,4 +116,73 @@ public void secureResponseImmediateTest() throws Exception { verify(encoder, times(1)).toMessageContent(data); verify(encoder, times(sessionCount)).encryptMessageContent(any(byte[].class), any(byte[].class)); } + + @Test + public void packMessage() throws Exception { + // given + EzyObjectToByteEncoder encoder = mock(EzyObjectToByteEncoder.class); + EzySocketResponseApi instance = new EzySocketResponseApi(encoder); + + byte[] message = RandomUtil.randomShortByteArray(); + EzyChannel channel = mock(EzyChannel.class); + when(channel.pack(message)).thenReturn(message); + + EzySession session = mock(EzySession.class); + when(session.getChannel()).thenReturn(channel); + + // when + Object actual = MethodInvoker.create() + .object(instance) + .method("packMessage") + .param(EzySession.class, session) + .param(Object.class, message) + .invoke(); + + // then + Asserts.assertEquals(actual, message); + + verify(session, times(1)).getChannel(); + verifyNoMoreInteractions(session); + + verify(channel, times(1)).pack(message); + verifyNoMoreInteractions(channel); + } + + @Test + public void packMessageThrowsException() throws Exception { + // given + EzyObjectToByteEncoder encoder = mock(EzyObjectToByteEncoder.class); + EzySocketResponseApi instance = new EzySocketResponseApi(encoder); + + byte[] message = RandomUtil.randomShortByteArray(); + EzyChannel channel = mock(EzyChannel.class); + EzyConnectionCloseException error = new EzyConnectionCloseException("test"); + when(channel.pack(message)).thenThrow(error); + + EzySession session = mock(EzySession.class); + when(session.getChannel()).thenReturn(channel); + + // when + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(instance) + .method("packMessage") + .param(EzySession.class, session) + .param(Object.class, message) + .invoke() + ); + + // then + Asserts.assertEqualsType( + e.getCause().getCause(), + EzyConnectionCloseException.class + ); + + verify(session, times(1)).getChannel(); + verify(session, times(1)).disconnect(); + verifyNoMoreInteractions(session); + + verify(channel, times(1)).pack(message); + verifyNoMoreInteractions(channel); + } } From 6f94624e37c937c48664bc43ccae1ac97a4bb23e Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 14 Jul 2023 21:17:31 +0700 Subject: [PATCH 19/46] update EzyHandShakeControllerTest --- .../ezyfoxserver/controller/EzyHandshakeController.java | 4 ++-- .../testing/controller/EzyHandShakeControllerTest.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java index caec1552..2f2be838 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java @@ -39,12 +39,12 @@ protected void handleSocketSSL(EzyServerContext ctx, EzyHandshakeEvent event) { if (session.getConnectionType() == EzyConnectionType.WEBSOCKET) { return; } - boolean enableL7SSL = ctx + boolean enableCustomizationSsl = ctx .getServer() .getSettings() .getSocket() .isCustomizationSslActive(); - if (!enableL7SSL) { + if (!enableCustomizationSsl) { return; } if (!event.isEnableEncryption()) { diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java index 3ff322de..10a8e78a 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java @@ -143,7 +143,7 @@ public void handleSocketSSLButEventNoEncryptionTest() { EzySettings settings = mock(EzySettings.class); EzySocketSetting socketSetting = mock(EzySocketSetting.class); when(settings.getSocket()).thenReturn(socketSetting); - when(socketSetting.isSslActive()).thenReturn(true); + when(socketSetting.isCustomizationSslActive()).thenReturn(true); when(serverContext.getServer()).thenReturn(server); when(server.getSettings()).thenReturn(settings); @@ -174,7 +174,7 @@ public void handleSocketSSLButClientKeyEmptyTest() { EzySettings settings = mock(EzySettings.class); EzySocketSetting socketSetting = mock(EzySocketSetting.class); when(settings.getSocket()).thenReturn(socketSetting); - when(socketSetting.isSslActive()).thenReturn(true); + when(socketSetting.isCustomizationSslActive()).thenReturn(true); when(serverContext.getServer()).thenReturn(server); when(server.getSettings()).thenReturn(settings); @@ -219,7 +219,7 @@ public void handleSocketSSLButInvalidClientKeyEmptyTest() { EzySettings settings = mock(EzySettings.class); EzySocketSetting socketSetting = mock(EzySocketSetting.class); when(settings.getSocket()).thenReturn(socketSetting); - when(socketSetting.isSslActive()).thenReturn(true); + when(socketSetting.isCustomizationSslActive()).thenReturn(true); when(serverContext.getServer()).thenReturn(server); when(server.getSettings()).thenReturn(settings); @@ -277,7 +277,7 @@ public void handleSocketSSLButSessionKeyNotNullTest() { EzySettings settings = mock(EzySettings.class); EzySocketSetting socketSetting = mock(EzySocketSetting.class); when(settings.getSocket()).thenReturn(socketSetting); - when(socketSetting.isSslActive()).thenReturn(true); + when(socketSetting.isCustomizationSslActive()).thenReturn(true); when(serverContext.getServer()).thenReturn(server); when(server.getSettings()).thenReturn(settings); From e8af92f28fd2c010b68634fb89b0bf68297c701c Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 14 Jul 2023 21:49:05 +0700 Subject: [PATCH 20/46] add EzyChannelTest and SslByteBuffersTest --- .../testing/socket/EzyChannelTest.java | 66 +++++++++++++++++++ .../testing/ssl/SslByteBuffersTest.java | 46 +++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java new file mode 100644 index 00000000..75ec74a6 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java @@ -0,0 +1,66 @@ +package com.tvd12.ezyfoxserver.testing.socket; + +import com.tvd12.ezyfoxserver.constant.EzyConnectionType; +import com.tvd12.ezyfoxserver.socket.EzyChannel; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.util.RandomUtil; +import org.testng.annotations.Test; + +import java.net.SocketAddress; + +public class EzyChannelTest { + + @Test + public void pack() throws Exception { + // given + EzyChannel channel = new TestEzyChannel(); + byte[] bytes = RandomUtil.randomShortByteArray(); + + // when + byte[] actual = channel.pack(bytes); + + // then + Asserts.assertEquals(actual, bytes); + } + + public static class TestEzyChannel implements EzyChannel { + + @Override + public void close() { + } + + @Override + public void disconnect() { + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public int write(Object data, boolean binary) throws Exception { + return 0; + } + + @Override + public T getConnection() { + return null; + } + + @Override + public EzyConnectionType getConnectionType() { + return null; + } + + @Override + public SocketAddress getServerAddress() { + return null; + } + + @Override + public SocketAddress getClientAddress() { + return null; + } + } +} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java new file mode 100644 index 00000000..c339def4 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java @@ -0,0 +1,46 @@ +package com.tvd12.ezyfoxserver.testing.ssl; + +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.util.RandomUtil; +import org.testng.annotations.Test; + +import java.nio.ByteBuffer; + +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; + +public class SslByteBuffersTest { + + @Test + public void enlargeBufferTest() { + // given + ByteBuffer buffer = ByteBuffer.allocate(1); + int sessionProposedCapacity = RandomUtil.randomSmallInt() + 1; + + // when + ByteBuffer actual = enlargeBuffer( + buffer, + sessionProposedCapacity + ); + + // then + Asserts.assertEquals(actual.capacity(), sessionProposedCapacity); + } + + @Test + public void enlargeBufferIfNeedTest() { + // given + ByteBuffer buffer = ByteBuffer.allocate(3); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + + // when + ByteBuffer actual = enlargeBufferIfNeed( + buffer, + 1 + ); + + // then + Asserts.assertEquals(actual, buffer); + } +} From 0d5be6e5aa7cb6e8825da3a980c2536a5f208d6d Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 14 Jul 2023 22:10:19 +0700 Subject: [PATCH 21/46] add EzySocketChannels --- .../socket/EzySocketChannels.java | 26 +++++++++++++ .../ssl/EzySslHandshakeHandler.java | 11 ++---- .../testing/socket/EzySocketChannelsTest.java | 38 +++++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java new file mode 100644 index 00000000..5d6afff8 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java @@ -0,0 +1,26 @@ +package com.tvd12.ezyfoxserver.socket; + +import javax.net.ssl.SSLException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +public final class EzySocketChannels { + + private EzySocketChannels() {} + + public static void write( + SocketChannel socketChannel, + ByteBuffer buffer, + long timeoutAt + ) throws IOException { + buffer.flip(); + while (buffer.hasRemaining()) { + long currentTime = System.currentTimeMillis(); + if (currentTime >= timeoutAt) { + throw new SSLException("Timeout"); + } + socketChannel.write(buffer); + } + } +} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index 3f832fab..1593b71e 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -8,6 +8,7 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import static com.tvd12.ezyfoxserver.socket.EzySocketChannels.write; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; @@ -124,10 +125,7 @@ public SSLEngine handle( throw new SSLException("Buffer underflow occurred after a wrap."); case CLOSED: try { - netBuffer.flip(); - while (netBuffer.hasRemaining()) { - socketChannel.write(netBuffer); - } + write(socketChannel, netBuffer, endTime); peerNetData.clear(); } catch (Exception e) { logger.info( @@ -138,10 +136,7 @@ public SSLEngine handle( } break; default: // OK - netBuffer.flip(); - while (netBuffer.hasRemaining()) { - socketChannel.write(netBuffer); - } + write(socketChannel, netBuffer, endTime); break; } break; diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java new file mode 100644 index 00000000..050e6b70 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java @@ -0,0 +1,38 @@ +package com.tvd12.ezyfoxserver.testing.socket; + +import com.tvd12.ezyfoxserver.socket.EzySocketChannels; +import com.tvd12.test.assertion.Asserts; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class EzySocketChannelsTest { + + @Test + public void writeTimeOut() { + // given + long timeoutAt = System.currentTimeMillis() - 1000; + SocketChannel socketChannel = mock(SocketChannel.class); + ByteBuffer buffer = ByteBuffer.allocate(2); + buffer.put(new byte[] {1, 2}); + + // when + Throwable e = Asserts.assertThrows(() -> + EzySocketChannels.write( + socketChannel, + buffer, + timeoutAt + ) + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verifyNoMoreInteractions(socketChannel); + } +} From 139ee17e6df671e945a31e24cb6c7acc1d23ac80 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 14 Jul 2023 23:24:54 +0700 Subject: [PATCH 22/46] add EzySecureSocketDataReceiverTest --- .../socket/EzySecureSocketDataReceiver.java | 15 +- .../EzySecureSocketDataReceiverTest.java | 242 ++++++++++++++++++ 2 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 750e106a..629b5fa2 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -6,7 +6,6 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLSession; -import java.io.IOException; import java.nio.ByteBuffer; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; @@ -39,11 +38,6 @@ protected byte[] readTcpBytesFromBuffer( tcpNetBuffer.clear(); SSLEngineResult result = engine.unwrap(appBuffer, tcpNetBuffer); switch (result.getStatus()) { - case OK: - tcpNetBuffer.flip(); - byte[] binary = new byte[tcpNetBuffer.limit()]; - tcpNetBuffer.get(binary); - return binary; case BUFFER_OVERFLOW: appBuffer = enlargeBuffer(appBuffer, appBufferSize); break; @@ -55,10 +49,11 @@ protected byte[] readTcpBytesFromBuffer( throw new EzyConnectionCloseException( "ssl unwrap result status is CLOSE" ); - default: - throw new IOException( - "Invalid SSL status: " + result.getStatus() - ); + default: // 0K + tcpNetBuffer.flip(); + byte[] binary = new byte[tcpNetBuffer.limit()]; + tcpNetBuffer.get(binary); + return binary; } } return new byte[0]; diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java new file mode 100644 index 00000000..0d87833c --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java @@ -0,0 +1,242 @@ +package com.tvd12.ezyfoxserver.nio.testing.socket; + +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketChannel; +import com.tvd12.ezyfoxserver.nio.socket.EzySecureSocketDataReceiver; +import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; +import com.tvd12.ezyfoxserver.socket.EzyChannel; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.MethodInvoker; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.mockito.Mockito.*; + +public class EzySecureSocketDataReceiverTest { + + private ByteBuffer buffer; + private SSLEngine sslEngine; + private SSLSession sslSession; + private EzyNioSecureSocketChannel channel; + private EzyHandlerGroupManager handlerGroupManager; + private EzySecureSocketDataReceiver instance; + + @BeforeMethod + public void setup() { + int bufferSize = 128; + buffer = ByteBuffer.allocate(bufferSize); + sslEngine = mock(SSLEngine.class); + sslSession = mock(SSLSession.class); + channel = mock(EzyNioSecureSocketChannel.class); + handlerGroupManager = mock(EzyHandlerGroupManager.class); + instance = (EzySecureSocketDataReceiver) EzySecureSocketDataReceiver + .builder() + .threadPoolSize(1) + .handlerGroupManager(handlerGroupManager) + .build(); + when(channel.getEngine()).thenReturn(sslEngine); + when(sslEngine.getSession()).thenReturn(sslSession); + when(sslSession.getApplicationBufferSize()).thenReturn(bufferSize); + when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); + } + + @AfterMethod + public void verifyAll() { + verify(sslEngine, times(1)).getSession(); + verifyNoMoreInteractions(sslEngine); + + verify(sslSession, times(1)).getApplicationBufferSize(); + verify(sslSession, times(1)).getPacketBufferSize(); + verifyNoMoreInteractions(sslSession); + + verify(channel, times(1)).getEngine(); + verifyNoMoreInteractions(channel); + verifyNoMoreInteractions(handlerGroupManager); + } + + @Test + public void readTcpBytesFromBufferCaseOk() throws Exception { + // given + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(buffer); + return result; + }); + + // when + byte[] actual = MethodInvoker.create() + .object(instance) + .method("readTcpBytesFromBuffer") + .param(EzyChannel.class, channel) + .param(ByteBuffer.class, buffer) + .invoke(byte[].class); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferCaseBufferOverFlow() throws Exception { + // given + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger unwrapCallCount = new AtomicInteger(); + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(buffer); + return resultOk; + }); + + // when + byte[] actual = MethodInvoker.create() + .object(instance) + .method("readTcpBytesFromBuffer") + .param(EzyChannel.class, channel) + .param(ByteBuffer.class, buffer) + .invoke(byte[].class); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferCaseBufferUnderFlow() throws Exception { + // given + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger unwrapCallCount = new AtomicInteger(); + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(buffer); + return resultOk; + }); + + // when + byte[] actual = MethodInvoker.create() + .object(instance) + .method("readTcpBytesFromBuffer") + .param(EzyChannel.class, channel) + .param(ByteBuffer.class, buffer) + .invoke(byte[].class); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferCaseBufferClosed() throws Exception { + // given + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + + // when + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(instance) + .method("readTcpBytesFromBuffer") + .param(EzyChannel.class, channel) + .param(ByteBuffer.class, buffer) + .invoke(byte[].class) + ); + + // then + Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); + + verify(sslEngine, times(1)).closeOutbound(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferNoRemaining() { + // given + buffer.clear(); + buffer.flip(); + + // when + byte[] actual = MethodInvoker.create() + .object(instance) + .method("readTcpBytesFromBuffer") + .param(EzyChannel.class, channel) + .param(ByteBuffer.class, buffer) + .invoke(byte[].class); + + // then + Asserts.assertEquals(actual, new byte[0]); + } +} From 64df64ad4cc64b6f9a5e04fbc8d2a01441bbdb44 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 09:04:15 +0700 Subject: [PATCH 23/46] add EzyNioSecureSocketChannelTest --- .../tvd12/ezyfoxserver/ssl/EzySslEngines.java | 23 +++ .../testing/ssl/EzySslEnginesTest.java | 37 ++++ .../testing/ssl/SslByteBuffersTest.java | 2 +- .../nio/socket/EzyNioSecureSocketChannel.java | 15 +- .../socket/EzySecureSocketDataReceiver.java | 3 +- .../socket/EzyNioSecureSocketChannelTest.java | 186 ++++++++++++++++++ 6 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java create mode 100644 ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java new file mode 100644 index 00000000..c1bcd24c --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java @@ -0,0 +1,23 @@ +package com.tvd12.ezyfoxserver.ssl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLEngine; + +public final class EzySslEngines { + + private static final Logger LOGGER = LoggerFactory.getLogger( + EzySslEngines.class + ); + + private EzySslEngines() {} + + public static void safeCloseOutbound(SSLEngine sslEngine) { + try { + sslEngine.closeOutbound(); + } catch (Throwable e) { + LOGGER.info("close outbound error", e); + } + } +} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java new file mode 100644 index 00000000..3c76e0ae --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java @@ -0,0 +1,37 @@ +package com.tvd12.ezyfoxserver.testing.ssl; + +import org.testng.annotations.Test; + +import javax.net.ssl.SSLEngine; + +import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; +import static org.mockito.Mockito.*; + +public class EzySslEnginesTest { + + @Test + public void safeCloseOutboundTest() { + // given + SSLEngine engine = mock(SSLEngine.class); + + // when + safeCloseOutbound(engine); + + // then + verify(engine, times(1)).closeOutbound(); + } + + @Test + public void safeCloseOutboundCaseExceptionTest() { + // given + SSLEngine engine = mock(SSLEngine.class); + RuntimeException error = new RuntimeException("test"); + doThrow(error).when(engine).closeOutbound(); + + // when + safeCloseOutbound(engine); + + // then + verify(engine, times(1)).closeOutbound(); + } +} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java index c339def4..3f0cd51c 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java @@ -15,7 +15,7 @@ public class SslByteBuffersTest { public void enlargeBufferTest() { // given ByteBuffer buffer = ByteBuffer.allocate(1); - int sessionProposedCapacity = RandomUtil.randomSmallInt() + 1; + int sessionProposedCapacity = RandomUtil.randomSmallInt() + 2; // when ByteBuffer actual = enlargeBuffer( diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 8a6ac5d0..d1dc6b74 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -10,6 +10,7 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { @@ -37,23 +38,21 @@ public byte[] pack(byte[] bytes) throws Exception { netBuffer ); switch (result.getStatus()) { - case OK: - netBuffer.flip(); - byte[] answer = new byte[netBuffer.limit()]; - netBuffer.get(answer); - return answer; case BUFFER_OVERFLOW: netBuffer = enlargeBuffer(netBuffer, netBufferLength); break; case BUFFER_UNDERFLOW: throw new IOException("Buffer underflow occurred after a wrap"); case CLOSED: - engine.closeOutbound(); + safeCloseOutbound(engine); throw new EzyConnectionCloseException( "ssl wrap result status is CLOSE" ); - default: - throw new IOException("Invalid SSL status: " + result.getStatus()); + default: // OK + netBuffer.flip(); + byte[] answer = new byte[netBuffer.limit()]; + netBuffer.get(answer); + return answer; } } return bytes; diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 629b5fa2..19c7ede3 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -8,6 +8,7 @@ import javax.net.ssl.SSLSession; import java.nio.ByteBuffer; +import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; @@ -45,7 +46,7 @@ protected byte[] readTcpBytesFromBuffer( tcpNetBuffer = enlargeBufferIfNeed(tcpNetBuffer, packageBufferSize); break; case CLOSED: - engine.closeOutbound(); + safeCloseOutbound(engine); throw new EzyConnectionCloseException( "ssl unwrap result status is CLOSE" ); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java new file mode 100644 index 00000000..f75763fa --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java @@ -0,0 +1,186 @@ +package com.tvd12.ezyfoxserver.nio.testing.socket; + +import com.tvd12.ezyfox.io.EzyByteBuffers; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketChannel; +import com.tvd12.test.assertion.Asserts; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +public class EzyNioSecureSocketChannelTest { + + private byte[] bytes; + private SSLEngine sslEngine; + private SSLSession sslSession; + private SocketChannel socketChannel; + private EzyNioSecureSocketChannel instance; + + @BeforeMethod + public void setup() { + int bufferSize = 128; + bytes = new byte[] {1, 2, 3}; + sslEngine = mock(SSLEngine.class); + sslSession = mock(SSLSession.class); + socketChannel = mock(SocketChannel.class); + instance = new EzyNioSecureSocketChannel( + socketChannel, + sslEngine + ); + when(sslEngine.getSession()).thenReturn(sslSession); + when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); + } + + @AfterMethod + public void verifyAll() throws Exception { + Asserts.assertEquals(instance.getEngine(), sslEngine); + + verify(sslEngine, times(1)).getSession(); + verifyNoMoreInteractions(sslEngine); + + verify(sslSession, times(1)).getPacketBufferSize(); + verifyNoMoreInteractions(sslSession); + + verify(socketChannel, times(1)).getLocalAddress(); + verify(socketChannel, times(1)).getRemoteAddress(); + verifyNoMoreInteractions(socketChannel); + } + + @Test + public void packCaseOk() throws Exception { + // given + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + ByteBuffer inputBuffer = it.getArgumentAt(0, ByteBuffer.class); + ByteBuffer netBuffer = it.getArgumentAt(1, ByteBuffer.class); + netBuffer.put(EzyByteBuffers.getBytes(inputBuffer)); + return result; + }); + + // when + byte[] actual = instance.pack(bytes); + + // then + Asserts.assertEquals(actual, bytes); + + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferCaseBufferOverFlow() throws Exception { + // given + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger wrapCallCount = new AtomicInteger(); + when(sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = wrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer inputBuffer = it.getArgumentAt(0, ByteBuffer.class); + ByteBuffer netBuffer = it.getArgumentAt(1, ByteBuffer.class); + netBuffer.put(EzyByteBuffers.getBytes(inputBuffer)); + return resultOk; + }); + + // when + byte[] actual = instance.pack(bytes); + + // then + Asserts.assertEquals(actual, bytes); + + verify(sslEngine, times(2)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferCaseBufferUnderFlow() throws Exception { + // given + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.pack(bytes) + ); + + // then + Asserts.assertEqualsType(e, IOException.class); + + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferCaseBufferClosed() throws Exception { + // given + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.pack(bytes) + ); + + // then + Asserts.assertEqualsType(e, EzyConnectionCloseException.class); + + verify(sslEngine, times(1)).closeOutbound(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readTcpBytesFromBufferNoRemaining() throws Exception { + // when + byte[] actual = instance.pack(new byte[0]); + + // then + Asserts.assertEquals(actual, new byte[0]); + } +} From b240df38db184b3b0eeefc03194bbac80505aeb1 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 14:34:30 +0700 Subject: [PATCH 24/46] update bootstrap and test --- .../nio/EzyAbstractSocketServerBootstrap.java | 20 ++--- .../nio/EzySocketServerBootstrap.java | 58 +++---------- .../nio/EzyUdpServerBootstrap.java | 71 ++++++--------- .../nio/EzyWebSocketServerBootstrap.java | 35 +++----- .../nio/socket/EzyNioSocketAcceptor.java | 47 +++++----- .../EzyAbstractSocketServerBootstrapTest.java | 31 +++++++ .../testing/EzySocketServerBootstrapTest.java | 86 +++++++++++++++++++ 7 files changed, 197 insertions(+), 151 deletions(-) create mode 100644 ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyAbstractSocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyAbstractSocketServerBootstrap.java index 973764a1..94b1b0f1 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyAbstractSocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyAbstractSocketServerBootstrap.java @@ -3,11 +3,10 @@ import com.tvd12.ezyfox.builder.EzyBuilder; import com.tvd12.ezyfox.util.EzyDestroyable; import com.tvd12.ezyfox.util.EzyStartable; +import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.context.EzyServerContext; import com.tvd12.ezyfoxserver.nio.socket.EzySocketDataReceiver; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; -import com.tvd12.ezyfoxserver.nio.wrapper.EzyNioSessionManager; -import com.tvd12.ezyfoxserver.setting.EzySessionManagementSetting; import com.tvd12.ezyfoxserver.setting.EzySettings; import com.tvd12.ezyfoxserver.socket.EzySessionTicketsQueue; import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopHandler; @@ -16,6 +15,8 @@ public abstract class EzyAbstractSocketServerBootstrap implements EzyStartable, EzyDestroyable { + protected EzyServer server; + protected EzySettings serverSettings; protected EzyServerContext serverContext; protected EzySocketDataReceiver socketDataReceiver; protected EzyHandlerGroupManager handlerGroupManager; @@ -24,6 +25,8 @@ public abstract class EzyAbstractSocketServerBootstrap implements EzyStartable, public EzyAbstractSocketServerBootstrap(Builder builder) { this.serverContext = builder.serverContext; + this.server = this.serverContext.getServer(); + this.serverSettings = this.server.getSettings(); this.socketDataReceiver = builder.socketDataReceiver; this.handlerGroupManager = builder.handlerGroupManager; this.sessionTicketsQueue = builder.sessionTicketsQueue; @@ -34,19 +37,6 @@ public void destroy() { processWithLogException(() -> writingLoopHandler.destroy()); } - protected final EzySettings getServerSettings() { - return serverContext.getServer().getSettings(); - } - - protected final EzyNioSessionManager getSessionManager() { - return (EzyNioSessionManager) - serverContext.getServer().getSessionManager(); - } - - protected final EzySessionManagementSetting getSessionManagementSetting() { - return getServerSettings().getSessionManagement(); - } - @SuppressWarnings("unchecked") public abstract static class Builder implements EzyBuilder { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 33af8f32..3ecd890f 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -27,10 +27,12 @@ public class EzySocketServerBootstrap extends EzyAbstractSocketServerBootstrap { private EzySocketEventLoopHandler readingLoopHandler; private EzySocketEventLoopHandler socketAcceptanceLoopHandler; private final SSLContext sslContext; + private final EzySocketSetting socketSetting; public EzySocketServerBootstrap(Builder builder) { super(builder); this.sslContext = builder.sslContext; + this.socketSetting = serverSettings.getSocket(); } public static Builder builder() { @@ -67,7 +69,12 @@ private void newAndConfigServerSocketChannel() throws Exception { private void getBindAndConfigServerSocket() throws Exception { this.serverSocket = serverSocketChannel.socket(); this.serverSocket.setReuseAddress(true); - this.serverSocket.bind(new InetSocketAddress(getSocketAddress(), getSocketPort())); + this.serverSocket.bind( + new InetSocketAddress( + socketSetting.getAddress(), + socketSetting.getPort() + ) + ); this.serverSocketChannel.register(acceptSelector, SelectionKey.OP_ACCEPT); } @@ -83,19 +90,19 @@ private void startSocketHandlers() throws Exception { private EzyNioSocketAcceptor newSocketAcceptor() { EzyNioSocketAcceptor acceptor; - if (isEnableL4Ssl()) { + if (socketSetting.isCertificationSslActive()) { acceptor = new EzyNioSocketAcceptor( - getSslConnectionAcceptorThreadPoolSize() + socketSetting.getSslConnectionAcceptorThreadPoolSize() ); acceptor.setSslHandshakeHandler( new EzySslHandshakeHandler( sslContext, - getSslHandshakeTimeout() + socketSetting.getSslHandshakeTimeout() ) ); } else { acceptor = new EzyNioSocketAcceptor( - getConnectionAcceptorThreadPoolSize() + socketSetting.getConnectionAcceptorThreadPoolSize() ); } return acceptor; @@ -103,7 +110,7 @@ private EzyNioSocketAcceptor newSocketAcceptor() { private EzySocketEventLoopHandler newWritingLoopHandler() { EzySocketWritingLoopHandler loopHandler = new EzySocketWritingLoopHandler(); - loopHandler.setThreadPoolSize(getSocketWriterThreadPoolSize()); + loopHandler.setThreadPoolSize(socketSetting.getWriterThreadPoolSize()); loopHandler.setEventHandlerSupplier(() -> { EzySocketWriter eventHandler = new EzyNioSocketWriter(); eventHandler.setWriterGroupFetcher(handlerGroupManager); @@ -130,7 +137,7 @@ private EzySocketEventLoopHandler newSocketAcceptanceLoopHandler( EzyNioSocketAcceptor socketAcceptor) { EzySocketEventLoopOneHandler loopHandler = new EzyNioSocketAcceptanceLoopHandler(); loopHandler.setThreadPoolSize(getSocketAcceptorThreadPoolSize()); - socketAcceptor.setTcpNoDelay(getSocketTcpNoDelay()); + socketAcceptor.setTcpNoDelay(socketSetting.isTcpNoDelay()); socketAcceptor.setReadSelector(readSelector); socketAcceptor.setOwnSelector(acceptSelector); socketAcceptor.setHandlerGroupManager(handlerGroupManager); @@ -146,51 +153,14 @@ private ServerSocketChannel newServerSocketChannel() throws Exception { return ServerSocketChannel.open(); } - private boolean isEnableL4Ssl() { - EzySocketSetting setting = getSocketSetting(); - return setting.isCertificationSslActive(); - } - - public int getSslHandshakeTimeout() { - return getSocketSetting().getSslHandshakeTimeout(); - } - private int getSocketReaderThreadPoolSize() { return EzyNioThreadPoolSizes.SOCKET_READER; } - private int getSocketWriterThreadPoolSize() { - return getSocketSetting().getWriterThreadPoolSize(); - } - - private int getConnectionAcceptorThreadPoolSize() { - return getSocketSetting().getConnectionAcceptorThreadPoolSize(); - } - - private int getSslConnectionAcceptorThreadPoolSize() { - return getSocketSetting().getSslConnectionAcceptorThreadPoolSize(); - } - private int getSocketAcceptorThreadPoolSize() { return EzyNioThreadPoolSizes.SOCKET_ACCEPTOR; } - private int getSocketPort() { - return getSocketSetting().getPort(); - } - - private String getSocketAddress() { - return getSocketSetting().getAddress(); - } - - private boolean getSocketTcpNoDelay() { - return getSocketSetting().isTcpNoDelay(); - } - - private EzySocketSetting getSocketSetting() { - return getServerSettings().getSocket(); - } - public static class Builder extends EzyAbstractSocketServerBootstrap.Builder { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyUdpServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyUdpServerBootstrap.java index 2aef3c52..bd0f511c 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyUdpServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyUdpServerBootstrap.java @@ -3,7 +3,7 @@ import com.tvd12.ezyfox.builder.EzyBuilder; import com.tvd12.ezyfox.util.EzyDestroyable; import com.tvd12.ezyfox.util.EzyStartable; -import com.tvd12.ezyfoxserver.api.EzyResponseApi; +import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.context.EzyServerContext; import com.tvd12.ezyfoxserver.nio.constant.EzyNioThreadPoolSizes; import com.tvd12.ezyfoxserver.nio.handler.EzyNioUdpDataHandler; @@ -12,12 +12,10 @@ import com.tvd12.ezyfoxserver.nio.udp.EzyNioUdpReader; import com.tvd12.ezyfoxserver.nio.udp.EzyNioUdpReadingLoopHandler; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; -import com.tvd12.ezyfoxserver.setting.EzySettings; import com.tvd12.ezyfoxserver.setting.EzyUdpSetting; import com.tvd12.ezyfoxserver.socket.EzyDatagramChannelPool; import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopHandler; import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopOneHandler; -import com.tvd12.ezyfoxserver.wrapper.EzySessionManager; import java.net.InetSocketAddress; import java.nio.channels.Selector; @@ -27,7 +25,8 @@ public class EzyUdpServerBootstrap implements EzyStartable, EzyDestroyable { private Selector readSelector; - private final EzyServerContext serverContext; + private final EzyServer server; + private final EzyUdpSetting udpSetting; private final EzySocketDataReceiver socketDataReceiver; private final EzyHandlerGroupManager handlerGroupManager; private final EzyDatagramChannelPool udpChannelPool; @@ -35,12 +34,14 @@ public class EzyUdpServerBootstrap implements EzyStartable, EzyDestroyable { private EzySocketEventLoopHandler readingLoopHandler; public EzyUdpServerBootstrap(Builder builder) { - this.serverContext = builder.serverContext; + EzyServerContext serverContext = builder.serverContext; + this.server = serverContext.getServer(); + this.udpSetting = server.getSettings().getUdp(); this.socketDataReceiver = builder.socketDataReceiver; this.handlerGroupManager = builder.handlerGroupManager; this.udpChannelPool = newChannelPool(); this.udpDataHandler = newUdpDataHandler(); - this.serverContext.setProperty(EzyNioUdpDataHandler.class, udpDataHandler); + serverContext.setProperty(EzyNioUdpDataHandler.class, udpDataHandler); } public static Builder builder() { @@ -65,12 +66,18 @@ private void openSelectors() throws Exception { } private EzyDatagramChannelPool newChannelPool() { - int poolSize = getUdpSetting().getChannelPoolSize(); - return new EzyDatagramChannelPool(poolSize); + return new EzyDatagramChannelPool( + udpSetting.getChannelPoolSize() + ); } private void configChannelPool() { - this.udpChannelPool.bind(new InetSocketAddress(getUdpAddress(), getUdpPort())); + this.udpChannelPool.bind( + new InetSocketAddress( + udpSetting.getAddress(), + udpSetting.getPort() + ) + ); this.udpChannelPool.register(readSelector); } @@ -82,7 +89,9 @@ private void startSocketHandlers() throws Exception { private EzySocketEventLoopHandler newReadingLoopHandler() { EzySocketEventLoopOneHandler loopHandler = new EzyNioUdpReadingLoopHandler(); loopHandler.setThreadPoolSize(getSocketReaderPoolSize()); - EzyNioUdpReader eventHandler = new EzyNioUdpReader(getUdpMaxRequestSize()); + EzyNioUdpReader eventHandler = new EzyNioUdpReader( + udpSetting.getMaxRequestSize() + ); eventHandler.setOwnSelector(readSelector); eventHandler.setUdpDataHandler(udpDataHandler); loopHandler.setEventHandler(eventHandler); @@ -90,11 +99,12 @@ private EzySocketEventLoopHandler newReadingLoopHandler() { } private EzyNioUdpDataHandler newUdpDataHandler() { - int handlerThreadPoolSize = getSocketHandlerPoolSize(); - EzySimpleNioUdpDataHandler handler = new EzySimpleNioUdpDataHandler(handlerThreadPoolSize); - handler.setResponseApi(getResponseApi()); + EzySimpleNioUdpDataHandler handler = new EzySimpleNioUdpDataHandler( + udpSetting.getHandlerThreadPoolSize() + ); + handler.setResponseApi(server.getResponseApi()); handler.setDatagramChannelPool(udpChannelPool); - handler.setSessionManager(getSessionManager()); + handler.setSessionManager(server.getSessionManager()); handler.setSocketDataReceiver(socketDataReceiver); handler.setHandlerGroupManager(handlerGroupManager); return handler; @@ -108,39 +118,6 @@ private int getSocketReaderPoolSize() { return EzyNioThreadPoolSizes.SOCKET_READER; } - private int getSocketHandlerPoolSize() { - return getUdpSetting().getHandlerThreadPoolSize(); - } - - private int getUdpPort() { - return getUdpSetting().getPort(); - } - - private String getUdpAddress() { - return getUdpSetting().getAddress(); - } - - private int getUdpMaxRequestSize() { - return getUdpSetting().getMaxRequestSize(); - } - - private EzyUdpSetting getUdpSetting() { - return getServerSettings().getUdp(); - } - - private EzySettings getServerSettings() { - return serverContext.getServer().getSettings(); - } - - private EzyResponseApi getResponseApi() { - return serverContext.getServer().getResponseApi(); - } - - @SuppressWarnings("rawtypes") - private EzySessionManager getSessionManager() { - return serverContext.getServer().getSessionManager(); - } - public static class Builder implements EzyBuilder { private EzyServerContext serverContext; diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyWebSocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyWebSocketServerBootstrap.java index cab367f1..b3f85807 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyWebSocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyWebSocketServerBootstrap.java @@ -3,6 +3,7 @@ import com.tvd12.ezyfoxserver.nio.builder.impl.EzyWebSocketSecureServerCreator; import com.tvd12.ezyfoxserver.nio.builder.impl.EzyWebSocketServerCreator; import com.tvd12.ezyfoxserver.nio.websocket.EzyWsWritingLoopHandler; +import com.tvd12.ezyfoxserver.nio.wrapper.EzyNioSessionManager; import com.tvd12.ezyfoxserver.setting.EzyWebSocketSetting; import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopHandler; import com.tvd12.ezyfoxserver.socket.EzySocketWriter; @@ -14,12 +15,14 @@ public class EzyWebSocketServerBootstrap extends EzyAbstractSocketServerBootstrap { - private Server server; + private Server websocketServer; private final SSLContext sslContext; + private final EzyWebSocketSetting webSocketSetting; public EzyWebSocketServerBootstrap(Builder builder) { super(builder); this.sslContext = builder.sslContext; + this.webSocketSetting = serverSettings.getWebsocket(); } public static Builder builder() { @@ -28,8 +31,8 @@ public static Builder builder() { @Override public void start() throws Exception { - server = newSocketServer(); - server.start(); + websocketServer = newWebsocketServer(); + websocketServer.start(); writingLoopHandler = newWritingLoopHandler(); writingLoopHandler.start(); } @@ -37,22 +40,22 @@ public void start() throws Exception { @Override public void destroy() { processWithLogException(() -> writingLoopHandler.destroy()); - processWithLogException(() -> server.stop()); + processWithLogException(() -> websocketServer.stop()); } - private Server newSocketServer() { + private Server newWebsocketServer() { return newSocketServerCreator() - .setting(getWsSetting()) - .sessionManager(getSessionManager()) + .setting(webSocketSetting) + .sessionManagementSetting(serverSettings.getSessionManagement()) + .sessionManager((EzyNioSessionManager) server.getSessionManager()) .handlerGroupManager(handlerGroupManager) - .sessionManagementSetting(getSessionManagementSetting()) .socketDataReceiver(socketDataReceiver) .create(); } private EzySocketEventLoopHandler newWritingLoopHandler() { EzyWsWritingLoopHandler loopHandler = new EzyWsWritingLoopHandler(); - loopHandler.setThreadPoolSize(getSocketWriterPoolSize()); + loopHandler.setThreadPoolSize(webSocketSetting.getWriterThreadPoolSize()); loopHandler.setEventHandlerSupplier(() -> { EzySocketWriter eventHandler = new EzySocketWriter(); eventHandler.setWriterGroupFetcher(handlerGroupManager); @@ -63,24 +66,12 @@ private EzySocketEventLoopHandler newWritingLoopHandler() { } private EzyWebSocketServerCreator newSocketServerCreator() { - if (isSslActive()) { + if (webSocketSetting.isSslActive()) { return new EzyWebSocketSecureServerCreator(sslContext); } return new EzyWebSocketServerCreator(); } - private int getSocketWriterPoolSize() { - return getWsSetting().getWriterThreadPoolSize(); - } - - private boolean isSslActive() { - return getWsSetting().isSslActive(); - } - - private EzyWebSocketSetting getWsSetting() { - return getServerSettings().getWebsocket(); - } - public static class Builder extends EzyAbstractSocketServerBootstrap.Builder { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index adc65de3..6c22b192 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -11,7 +11,6 @@ import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import lombok.Setter; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; @@ -38,7 +37,6 @@ public class EzyNioSocketAcceptor protected Selector readSelector; @Setter protected EzyHandlerGroupManager handlerGroupManager; - protected SSLContext sslContext; @Setter protected EzySslHandshakeHandler sslHandshakeHandler; protected final List acceptableConnections; @@ -70,27 +68,6 @@ public void handleEvent() { } } - public void handleAcceptableConnections() throws Exception { - synchronized (acceptableConnections) { - acceptableConnectionsBuffer.addAll(acceptableConnections); - acceptableConnections.clear(); - } - CountDownLatch countDownLatch = new CountDownLatch( - acceptableConnectionsBuffer.size() - ); - try { - for (SocketChannel clientChannel : acceptableConnectionsBuffer) { - connectionAcceptorExecutorService.execute(() -> { - acceptConnection(clientChannel); - countDownLatch.countDown(); - }); - } - countDownLatch.await(); - } finally { - acceptableConnectionsBuffer.clear(); - } - } - private void processReadyKeys() throws Exception { ownSelector.select(); @@ -118,6 +95,30 @@ private void addConnection(SocketChannel clientChannel) { } } + public void handleAcceptableConnections() throws Exception { + synchronized (acceptableConnections) { + acceptableConnectionsBuffer.addAll(acceptableConnections); + acceptableConnections.clear(); + } + CountDownLatch countDownLatch = new CountDownLatch( + acceptableConnectionsBuffer.size() + ); + try { + for (SocketChannel clientChannel : acceptableConnectionsBuffer) { + connectionAcceptorExecutorService.execute(() -> { + try { + acceptConnection(clientChannel); + } finally { + countDownLatch.countDown(); + } + }); + } + countDownLatch.await(); + } finally { + acceptableConnectionsBuffer.clear(); + } + } + private void acceptConnection(SocketChannel clientChannel) { try { doAcceptConnection(clientChannel); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzyAbstractSocketServerBootstrapTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzyAbstractSocketServerBootstrapTest.java index 19b49ca7..f9c9ff88 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzyAbstractSocketServerBootstrapTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzyAbstractSocketServerBootstrapTest.java @@ -1,19 +1,50 @@ package com.tvd12.ezyfoxserver.nio.testing; +import com.tvd12.ezyfoxserver.EzyServer; +import com.tvd12.ezyfoxserver.context.EzyServerContext; import com.tvd12.ezyfoxserver.nio.EzyAbstractSocketServerBootstrap; +import com.tvd12.ezyfoxserver.setting.EzySettings; import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopHandler; import com.tvd12.test.reflect.FieldUtil; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.mockito.Mockito.*; public class EzyAbstractSocketServerBootstrapTest { + private EzyServer server; + private EzySettings settings; + private EzyServerContext serverContext; + + @BeforeMethod + public void setup() { + serverContext = mock(EzyServerContext.class); + server = mock(EzyServer.class); + settings = mock(EzySettings.class); + + when(serverContext.getServer()).thenReturn(server); + when(server.getSettings()).thenReturn(settings); + } + + @AfterMethod + public void verifyAll() { + verify(serverContext, times(1)).getServer(); + verifyNoMoreInteractions(serverContext); + + verify(server, times(1)).getSettings(); + verifyNoMoreInteractions(server); + + verifyNoMoreInteractions(settings); + } + @Test public void destroyTest() { // given EzySocketEventLoopHandler writingLoopHandler = mock(EzySocketEventLoopHandler.class); InternalBoostrap sut = new InternalBoostrap.Builder() + .serverContext(serverContext) .build(); FieldUtil.setFieldValue(sut, "writingLoopHandler", writingLoopHandler); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java new file mode 100644 index 00000000..daf363c8 --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java @@ -0,0 +1,86 @@ +package com.tvd12.ezyfoxserver.nio.testing; + +import com.tvd12.ezyfoxserver.EzyServer; +import com.tvd12.ezyfoxserver.context.EzyServerContext; +import com.tvd12.ezyfoxserver.nio.EzySocketServerBootstrap; +import com.tvd12.ezyfoxserver.nio.socket.EzyNioSocketAcceptor; +import com.tvd12.ezyfoxserver.setting.EzySettings; +import com.tvd12.ezyfoxserver.setting.EzySocketSetting; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.FieldUtil; +import com.tvd12.test.reflect.MethodInvoker; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLContext; + +import static org.mockito.Mockito.*; + +public class EzySocketServerBootstrapTest { + + private EzyServer server; + private EzySettings settings; + private EzySocketSetting socketSetting; + private SSLContext sslContext; + private EzyServerContext serverContext; + private EzySocketServerBootstrap instance; + + @BeforeMethod + public void setup() { + sslContext = mock(SSLContext.class); + serverContext = mock(EzyServerContext.class); + server = mock(EzyServer.class); + settings = mock(EzySettings.class); + socketSetting = mock(EzySocketSetting.class); + + when(serverContext.getServer()).thenReturn(server); + when(server.getSettings()).thenReturn(settings); + when(settings.getSocket()).thenReturn(socketSetting); + + instance = EzySocketServerBootstrap.builder() + .sslContext(sslContext) + .serverContext(serverContext) + .build(); + } + + @AfterMethod + public void verifyAll() { + verify(serverContext, times(1)).getServer(); + verifyNoMoreInteractions(serverContext); + + verify(server, times(1)).getSettings(); + verifyNoMoreInteractions(server); + + verify(settings, times(1)).getSocket(); + verifyNoMoreInteractions(settings); + + verifyNoMoreInteractions(socketSetting); + verifyNoMoreInteractions(sslContext); + } + + @Test + public void newSocketAcceptorCaseCertificationSsl() { + // given + when(socketSetting.isCertificationSslActive()).thenReturn(true); + when(socketSetting.getSslConnectionAcceptorThreadPoolSize()).thenReturn(1); + + // when + EzyNioSocketAcceptor acceptor = MethodInvoker.create() + .object(instance) + .method("newSocketAcceptor") + .invoke(EzyNioSocketAcceptor.class); + + // then + Asserts.assertNotNull( + FieldUtil.getFieldValue( + acceptor, + "sslHandshakeHandler" + ) + ); + + verify(socketSetting, times(1)).isCertificationSslActive(); + verify(socketSetting, times(1)).getSslConnectionAcceptorThreadPoolSize(); + verify(socketSetting, times(1)).getSslHandshakeTimeout(); + } +} From 3407e8d892a06cf0df34d697f59e532c1f6f2221 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 14:59:09 +0700 Subject: [PATCH 25/46] update EzyNioSocketAcceptorTest --- .../socket/EzyNioSocketAcceptorTest.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java index 961d92cb..24e7c8f5 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java @@ -22,13 +22,17 @@ import com.tvd12.ezyfoxserver.setting.EzySimpleSettings; import com.tvd12.ezyfoxserver.setting.EzySimpleStreamingSetting; import com.tvd12.ezyfoxserver.socket.*; +import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import com.tvd12.ezyfoxserver.statistics.EzySimpleStatistics; import com.tvd12.ezyfoxserver.statistics.EzyStatistics; +import com.tvd12.test.assertion.Asserts; import com.tvd12.test.base.BaseTest; import com.tvd12.test.reflect.FieldUtil; import com.tvd12.test.reflect.MethodInvoker; import org.testng.annotations.Test; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; @@ -181,6 +185,106 @@ public void acceptConnectionTest() throws Exception { // then verify(handlerGroupManager, times(1)).newHandlerGroup(any(), any()); + verifyNoMoreInteractions(handlerGroupManager); + + verify(handlerGroup, times(1)).getSession(); + verifyNoMoreInteractions(handlerGroup); + + verify(session, times(1)).setProperty( + any(String.class), + any(SelectionKey.class) + ); + verifyNoMoreInteractions(session); + } + + @Test + public void acceptConnectionSslTest() throws Exception { + // given + EzyHandlerGroupManager handlerGroupManager = mock(EzyHandlerGroupManager.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + + Selector readSelector = Selector.open(); + EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(1); + sut.setReadSelector(readSelector); + SocketChannel clientChannel = SocketChannel.open(); + + when(handlerGroupManager.newHandlerGroup(any(), any())).thenReturn(handlerGroup); + sut.setHandlerGroupManager(handlerGroupManager); + + EzyNioSession session = mock(EzyNioSession.class); + when(handlerGroup.getSession()).thenReturn(session); + + EzySslHandshakeHandler sslHandshakeHandler = + mock(EzySslHandshakeHandler.class); + SSLEngine sslEngine = mock(SSLEngine.class); + when(sslHandshakeHandler.handle(clientChannel)) + .thenReturn(sslEngine); + sut.setSslHandshakeHandler(sslHandshakeHandler); + + // when + MethodInvoker.create() + .object(sut) + .method("acceptConnection") + .param(SocketChannel.class, clientChannel) + .call(); + + // then + verify(handlerGroupManager, times(1)).newHandlerGroup(any(), any()); + verifyNoMoreInteractions(handlerGroupManager); + + verify(handlerGroup, times(1)).getSession(); + verifyNoMoreInteractions(handlerGroup); + + verify(session, times(1)).setProperty( + any(String.class), + any(SelectionKey.class) + ); + verifyNoMoreInteractions(session); + + verify(sslHandshakeHandler, times(1)) + .handle(clientChannel); + verifyNoMoreInteractions(sslHandshakeHandler); + verifyNoMoreInteractions(sslEngine); + } + + @Test + public void acceptConnectionSslExceptionTest() throws Exception { + // given + EzyHandlerGroupManager handlerGroupManager = mock(EzyHandlerGroupManager.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + + Selector readSelector = Selector.open(); + EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(1); + sut.setReadSelector(readSelector); + SocketChannel clientChannel = SocketChannel.open(); + + when(handlerGroupManager.newHandlerGroup(any(), any())).thenReturn(handlerGroup); + sut.setHandlerGroupManager(handlerGroupManager); + + EzyNioSession session = mock(EzyNioSession.class); + when(handlerGroup.getSession()).thenReturn(session); + + EzySslHandshakeHandler sslHandshakeHandler = + mock(EzySslHandshakeHandler.class); + SSLException error = new SSLException("test"); + when(sslHandshakeHandler.handle(clientChannel)) + .thenThrow(error); + sut.setSslHandshakeHandler(sslHandshakeHandler); + + // when + MethodInvoker.create() + .object(sut) + .method("acceptConnection") + .param(SocketChannel.class, clientChannel) + .call(); + + // then + verifyNoMoreInteractions(handlerGroupManager); + verifyNoMoreInteractions(handlerGroup); + verifyNoMoreInteractions(session); + verify(sslHandshakeHandler, times(1)) + .handle(clientChannel); + verifyNoMoreInteractions(sslHandshakeHandler); } public static abstract class ExServerSocketChannel extends ServerSocketChannel { From 4dfe8ef4411a94797b8d5b68b3d0d91a3b3787f5 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 15:31:41 +0700 Subject: [PATCH 26/46] update EzyAbstractHandlerGroupTest --- .../handler/EzyAbstractHandlerGroupTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index 35d2ee8b..fe8e704e 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java @@ -10,6 +10,10 @@ import com.tvd12.ezyfoxserver.config.EzySimpleConfig; import com.tvd12.ezyfoxserver.constant.*; import com.tvd12.ezyfoxserver.context.EzySimpleServerContext; +import com.tvd12.ezyfoxserver.delegate.EzySessionDelegate; +import com.tvd12.ezyfoxserver.entity.EzyAbstractSession; +import com.tvd12.ezyfoxserver.entity.EzyDroppedPackets; +import com.tvd12.ezyfoxserver.entity.EzyImmediateDeliver; import com.tvd12.ezyfoxserver.entity.EzySession; import com.tvd12.ezyfoxserver.nio.entity.EzyNioSession; import com.tvd12.ezyfoxserver.nio.entity.EzySimpleSession; @@ -36,7 +40,9 @@ import org.testng.annotations.Test; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; import java.nio.channels.SocketChannel; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; @@ -293,6 +299,65 @@ public void writeUdpPacketToSocketClientAddress() throws Exception { verify(session, times(1)).getDatagramChannelPool(); } + @Test + public void writeUdpPacketToSocketClientAddressNormally() throws Exception { + // given + ExHandlerGroup sut = newHandlerGroup(); + + EzyDatagramChannelPool udpChannelPool = mock(EzyDatagramChannelPool.class); + EzySession session = FieldUtil.getFieldValue(sut, "session"); + when(session.getDatagramChannelPool()).thenReturn(udpChannelPool); + + DatagramChannel datagramChannel = mock(DatagramChannel.class); + when(udpChannelPool.getChannel()).thenReturn(datagramChannel); + + SocketAddress socketAddress = mock(SocketAddress.class); + when(session.getUdpClientAddress()).thenReturn(socketAddress); + + EzyPacket packet = mock(EzyPacket.class); + byte[] data = new byte[] {1, 2, 3}; + when(packet.getData()).thenReturn(data); + + ByteBuffer writeBuffer = ByteBuffer.wrap(new byte[0]); + + // when + MethodInvoker.create() + .object(sut) + .method("writeUdpPacketToSocket") + .param(EzyPacket.class, packet) + .param(Object.class, writeBuffer) + .call(); + + // then + verify(session, times(1)).getDatagramChannelPool(); + verify(session, times(1)).getUdpClientAddress(); + verify(session, times(2)).getChannel(); + verify((EzyAbstractSession) session, times(1)) + .setDelegate(any(EzySessionDelegate.class)); + verify((EzyAbstractSession) session, times(1)) + .setDisconnectionQueue(any(EzySocketDisconnectionQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setSessionTicketsQueue(any(EzySessionTicketsQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setDroppedPackets(any(EzyDroppedPackets.class)); + verify((EzyAbstractSession) session, times(1)) + .setImmediateDeliver(any(EzyImmediateDeliver.class)); + verifyNoMoreInteractions(session); + + verify(udpChannelPool, times(1)).getChannel(); + verifyNoMoreInteractions(udpChannelPool); + + verify(datagramChannel, times(1)).send( + any(ByteBuffer.class), + any(SocketAddress.class) + ); + verifyNoMoreInteractions(datagramChannel); + + verify(packet, times(1)).getData(); + + verifyNoMoreInteractions(socketAddress); + } + @Test public void getWriteBufferMaxTest() throws Exception { // given From 0a989599ae2a5853db2503a05f41efa0c86e3e38 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 15:45:11 +0700 Subject: [PATCH 27/46] update EzySimpleNioHandlerGroupTest --- .../handler/EzySimpleNioHandlerGroupTest.java | 135 +++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java index 37fcc92a..58f05ad0 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java @@ -11,7 +11,10 @@ import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.constant.EzyTransportType; import com.tvd12.ezyfoxserver.context.EzySimpleServerContext; +import com.tvd12.ezyfoxserver.delegate.EzySessionDelegate; import com.tvd12.ezyfoxserver.entity.EzyAbstractSession; +import com.tvd12.ezyfoxserver.entity.EzyDroppedPackets; +import com.tvd12.ezyfoxserver.entity.EzyImmediateDeliver; import com.tvd12.ezyfoxserver.entity.EzySession; import com.tvd12.ezyfoxserver.nio.builder.impl.EzyHandlerGroupBuilderFactoryImpl; import com.tvd12.ezyfoxserver.nio.entity.EzyNioSession; @@ -356,7 +359,6 @@ public void writePacketToSocketSessionKeyIsInvalid() throws Exception { when(session.getSelectionKey()).thenReturn(selectionKey); when(selectionKey.isValid()).thenReturn(false); - EzyPacket packet = mock(EzyPacket.class); when(packet.getData()).thenReturn(new byte[]{1, 2, 3, 5}); when(packet.isBinary()).thenReturn(true); @@ -372,8 +374,139 @@ public void writePacketToSocketSessionKeyIsInvalid() throws Exception { .call(); // then + verify(packet, times(1)).getData(); + verify(packet, times(1)).isBinary(); + verify(packet, times(1)).setFragment(any()); + verifyNoMoreInteractions(packet); + + verify(selectionKey, times(1)).isValid(); + verifyNoMoreInteractions(selectionKey); + verify(session, times(1)).getSelectionKey(); + verify(session, times(2)).getChannel(); + verify((EzyAbstractSession) session, times(1)) + .setDelegate(any(EzySessionDelegate.class)); + verify((EzyAbstractSession) session, times(1)) + .setDisconnectionQueue(any(EzySocketDisconnectionQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setSessionTicketsQueue(any(EzySessionTicketsQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setDroppedPackets(any(EzyDroppedPackets.class)); + verify((EzyAbstractSession) session, times(1)) + .setImmediateDeliver(any(EzyImmediateDeliver.class)); + verifyNoMoreInteractions(session); + + } + + @Test + public void writePacketToSocketSessionKeyIsValid() throws Exception { + // given + EzySimpleNioHandlerGroup sut = newHandlerGroup(); + + EzyNioSession session = FieldUtil.getFieldValue(sut, "session"); + SelectionKey selectionKey = mock(SelectionKey.class); + when(session.getSelectionKey()).thenReturn(selectionKey); + when(selectionKey.isValid()).thenReturn(true); + + EzyPacket packet = mock(EzyPacket.class); + when(packet.getData()).thenReturn(new byte[]{1, 2, 3, 5}); + when(packet.isBinary()).thenReturn(true); + + ByteBuffer writeBuffer = ByteBuffer.allocate(5); + + // when + MethodInvoker.create() + .object(sut) + .method("writePacketToSocket") + .param(EzyPacket.class, packet) + .param(Object.class, writeBuffer) + .call(); + + // then + verify(packet, times(1)).getData(); + verify(packet, times(1)).isBinary(); + verify(packet, times(1)).setFragment(any()); + verifyNoMoreInteractions(packet); + verify(selectionKey, times(1)).isValid(); + verify(selectionKey, times(1)).interestOps( + SelectionKey.OP_READ | SelectionKey.OP_WRITE + ); + verifyNoMoreInteractions(selectionKey); + + verify(session, times(1)).getSelectionKey(); + verify(session, times(2)).getChannel(); + verify((EzyAbstractSession) session, times(1)) + .setDelegate(any(EzySessionDelegate.class)); + verify((EzyAbstractSession) session, times(1)) + .setDisconnectionQueue(any(EzySocketDisconnectionQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setSessionTicketsQueue(any(EzySessionTicketsQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setDroppedPackets(any(EzyDroppedPackets.class)); + verify((EzyAbstractSession) session, times(1)) + .setImmediateDeliver(any(EzyImmediateDeliver.class)); + verifyNoMoreInteractions(session); + + } + + @Test + public void writePacketToSocketNormally() throws Exception { + // given + EzySimpleNioHandlerGroup sut = newHandlerGroup(); + + EzyNioSession session = FieldUtil.getFieldValue(sut, "session"); + SelectionKey selectionKey = mock(SelectionKey.class); + when(session.getSelectionKey()).thenReturn(selectionKey); + when(selectionKey.isValid()).thenReturn(false); + + EzyPacket packet = mock(EzyPacket.class); + byte[] data = new byte[] {1, 2, 3, 5}; + when(packet.getData()).thenReturn(data); + when(packet.isBinary()).thenReturn(true); + + EzyChannel channel = session.getChannel(); + when(channel.write(any(), anyBoolean())) + .thenReturn(data.length); + + ByteBuffer writeBuffer = ByteBuffer.allocate(5); + + // when + MethodInvoker.create() + .object(sut) + .method("writePacketToSocket") + .param(EzyPacket.class, packet) + .param(Object.class, writeBuffer) + .call(); + + // then + verify(packet, times(1)).getData(); + verify(packet, times(1)).isBinary(); + verify(packet, times(1)).release(); + verifyNoMoreInteractions(packet); + + verify(channel, times(1)).write( + any(), + anyBoolean() + ); + verify(channel, times(1)).getConnectionType(); + verify(channel, times(1)).getClientAddress(); + verify(channel, times(1)).getConnection(); + verifyNoMoreInteractions(channel); + + verify(session, times(3)).getChannel(); + verify((EzyAbstractSession) session, times(1)) + .setDelegate(any(EzySessionDelegate.class)); + verify((EzyAbstractSession) session, times(1)) + .setDisconnectionQueue(any(EzySocketDisconnectionQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setSessionTicketsQueue(any(EzySessionTicketsQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setDroppedPackets(any(EzyDroppedPackets.class)); + verify((EzyAbstractSession) session, times(1)) + .setImmediateDeliver(any(EzyImmediateDeliver.class)); + verifyNoMoreInteractions(session); + } private EzySimpleNioHandlerGroup newHandlerGroup() throws IOException { From 4c0fa78e68ff037dbb193edaa0a4ffcdfc6194ad Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 16:18:36 +0700 Subject: [PATCH 28/46] update EzySecureSocketDataReceiverTest --- .../EzySecureSocketDataReceiverTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java index 0d87833c..57e7001a 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java @@ -1,6 +1,7 @@ package com.tvd12.ezyfoxserver.nio.testing.socket; import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketChannel; import com.tvd12.ezyfoxserver.nio.socket.EzySecureSocketDataReceiver; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; @@ -15,6 +16,7 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLSession; import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Mockito.*; @@ -239,4 +241,51 @@ public void readTcpBytesFromBufferNoRemaining() { // then Asserts.assertEquals(actual, new byte[0]); } + + @Test + public void tcpReceiveCaseBufferClosed() throws Exception { + // given + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + + SocketChannel socketChannel = mock(SocketChannel.class); + byte[] bytes = new byte[] {1, 2, 3}; + when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer bf = it.getArgumentAt(0, ByteBuffer.class); + bf.clear(); + bf.put(bytes); + return bytes.length; + }); + + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + when(handlerGroupManager.getHandlerGroup(socketChannel)) + .thenReturn(handlerGroup); + when(handlerGroup.getChannel()).thenReturn(channel); + + // when + instance.tcpReceive(socketChannel); + Thread.sleep(300); + + // then + verify(handlerGroupManager, times(2)) + .getHandlerGroup(socketChannel); + + verify(socketChannel, times(1)).read(any(ByteBuffer.class)); + verifyNoMoreInteractions(socketChannel); + + verify(sslEngine, times(1)).closeOutbound(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + + verify(handlerGroup, times(1)).getChannel(); + verify(handlerGroup, times(1)).enqueueDisconnection(); + verifyNoMoreInteractions(handlerGroup); + } } From 2ca1ad0ad864f72ac1d6a01114a72bdc8a567617 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 16:34:18 +0700 Subject: [PATCH 29/46] update EzyNioServerBootstrapBuilderImplTest --- .../EzyNioServerBootstrapBuilderImpl.java | 18 +++--- .../EzyNioServerBootstrapBuilderImplTest.java | 62 +++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index 7938a068..f7d4355c 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -104,18 +104,22 @@ private ExecutorService newStatsThreadPool() { return EzyExecutors.newFixedThreadPool(threadPoolSize, "statistics"); } - private EzySocketDataReceiver newSocketDataReceiver(EzyHandlerGroupManager handlerGroupManager) { - EzySocketSetting setting = getSocketSetting(); - return ( - setting.isCertificationSslActive() - ? EzySecureSocketDataReceiver.builder() - : EzySocketDataReceiver.builder() - ) + private EzySocketDataReceiver newSocketDataReceiver( + EzyHandlerGroupManager handlerGroupManager + ) { + return newSocketDataReceiverBuilder() .handlerGroupManager(handlerGroupManager) .threadPoolSize(getThreadPoolSizeSetting().getSocketDataReceiver()) .build(); } + private EzySocketDataReceiver.Builder newSocketDataReceiverBuilder() { + EzySocketSetting setting = getSocketSetting(); + return setting.isCertificationSslActive() + ? EzySecureSocketDataReceiver.builder() + : EzySocketDataReceiver.builder(); + } + private EzySocketStreamQueue newStreamQueue() { return new EzyBlockingSocketStreamQueue(); } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java index 8f6a9338..940ae245 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java @@ -1,13 +1,24 @@ package com.tvd12.ezyfoxserver.nio.testing.builder; import com.tvd12.ezyfox.codec.EzyCodecCreator; +import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.EzySimpleServer; import com.tvd12.ezyfoxserver.config.EzySimpleConfig; import com.tvd12.ezyfoxserver.nio.builder.impl.EzyNioServerBootstrapBuilderImpl; +import com.tvd12.ezyfoxserver.nio.socket.EzySecureSocketDataReceiver; +import com.tvd12.ezyfoxserver.nio.socket.EzySocketDataReceiver; +import com.tvd12.ezyfoxserver.setting.EzyLoggerSetting; +import com.tvd12.ezyfoxserver.setting.EzySettings; import com.tvd12.ezyfoxserver.setting.EzySimpleSettings; +import com.tvd12.ezyfoxserver.setting.EzySocketSetting; +import com.tvd12.test.assertion.Asserts; import com.tvd12.test.base.BaseTest; +import com.tvd12.test.reflect.MethodInvoker; import org.testng.annotations.Test; +import static java.util.Collections.emptySet; +import static org.mockito.Mockito.*; + public class EzyNioServerBootstrapBuilderImplTest extends BaseTest { @Test @@ -24,6 +35,57 @@ public void test() { builder.build(); } + @Test + public void newSocketDataReceiverBuilderSslTest() { + // given + EzyNioServerBootstrapBuilderImpl instance = + new EzyNioServerBootstrapBuilderImpl(); + + EzyServer server = mock(EzySimpleServer.class); + EzySettings settings = mock(EzySettings.class); + when(server.getSettings()).thenReturn(settings); + + EzyLoggerSetting loggerSetting = mock(EzyLoggerSetting.class); + when(settings.getLogger()).thenReturn(loggerSetting); + + EzyLoggerSetting.EzyIgnoredCommandsSetting ignoredCommandsSetting = + mock(EzyLoggerSetting.EzyIgnoredCommandsSetting.class); + when(ignoredCommandsSetting.getCommands()).thenReturn(emptySet()); + when(loggerSetting.getIgnoredCommands()).thenReturn(ignoredCommandsSetting); + + EzySocketSetting socketSetting = mock(EzySocketSetting.class); + when(settings.getSocket()).thenReturn(socketSetting); + when(socketSetting.isCertificationSslActive()).thenReturn(true); + + instance.server(server); + + // when + EzySocketDataReceiver.Builder actual = MethodInvoker.create() + .object(instance) + .method("newSocketDataReceiverBuilder") + .invoke(EzySocketDataReceiver.Builder.class); + + // then + Asserts.assertEqualsType(actual, EzySecureSocketDataReceiver.Builder.class); + + verify(server, times(3)).getSettings(); + verifyNoMoreInteractions(server); + + verify(settings, times(1)).getSocket(); + verify(settings, times(1)).getLogger(); + verify(settings, times(1)).getZoneIds(); + verifyNoMoreInteractions(settings); + + verify(loggerSetting, times(1)).getIgnoredCommands(); + verifyNoMoreInteractions(loggerSetting); + + verify(ignoredCommandsSetting, times(1)).getCommands(); + verifyNoMoreInteractions(ignoredCommandsSetting); + + verify(socketSetting, times(1)).isCertificationSslActive(); + verifyNoMoreInteractions(socketSetting); + } + public static class ExCodecCreator implements EzyCodecCreator { @Override From ad6c481e2b90a635bfc84282929d3c200bd30b69 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 16:39:09 +0700 Subject: [PATCH 30/46] update EzyAbstractHandlerGroupTest --- .../handler/EzyAbstractHandlerGroupTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index fe8e704e..d385a44e 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java @@ -564,6 +564,80 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualTcp() throws verify(session, times(0)).getUdpClientAddress(); } + @Test + public void executeSendingPacketChannelIsNotConnected() { + // given + EzyStatistics statistics = new EzySimpleStatistics(); + EzySimpleSettings settings = new EzySimpleSettings(); + EzySimpleStreamingSetting streaming = settings.getStreaming(); + streaming.setEnable(true); + EzySimpleServer server = new EzySimpleServer(); + server.setSettings(settings); + EzySimpleConfig config = new EzySimpleConfig(); + server.setConfig(config); + + EzySimpleServerContext serverContext = new EzySimpleServerContext(); + serverContext.setServer(server); + serverContext.init(); + + EzyChannel channel = mock(EzyChannel.class); + + EzySimpleSession session = mock(EzySimpleSession.class); + when(session.getChannel()).thenReturn(channel); + when(session.isActivated()).thenReturn(true); + + ExEzyByteToObjectDecoder decoder = new ExEzyByteToObjectDecoder(); + ExecutorService statsThreadPool = EzyExecutors.newFixedThreadPool(1, "stats"); + + EzySessionTicketsRequestQueues sessionTicketsRequestQueues = + mock(EzySessionTicketsRequestQueues.class); + when(sessionTicketsRequestQueues.addRequest(any())).thenReturn(false); + ExHandlerGroup group = (ExHandlerGroup) new ExHandlerGroup.Builder() + .session(session) + .decoder(decoder) + .sessionCount(new AtomicInteger()) + .networkStats((EzyNetworkStats) statistics.getSocketStats().getNetworkStats()) + .sessionStats((EzySessionStats) statistics.getSocketStats().getSessionStats()) + .serverContext(serverContext) + .statsThreadPool(statsThreadPool) + .sessionTicketsRequestQueues(sessionTicketsRequestQueues) + .build(); + + EzyPacket packet = mock(EzyPacket.class); + when( + packet.getTransportType() + ).thenReturn(EzyTransportType.UDP_OR_TCP); + when(packet.getData()).thenReturn(new byte[]{1, 2, 3}); + ByteBuffer buffer = ByteBuffer.allocate(100); + + // when + MethodInvoker.create() + .object(group) + .method("executeSendingPacket") + .param(EzyPacket.class, packet) + .param(Object.class, buffer) + .invoke(); + + Asserts.assertNotNull(group.getSession()); + + // then + verify(channel, times(1)).isConnected(); + + verify(session, times(1)).isActivated(); + verify(session, times(2)).getChannel(); + verify((EzyAbstractSession) session, times(1)) + .setDelegate(any(EzySessionDelegate.class)); + verify((EzyAbstractSession) session, times(1)) + .setDisconnectionQueue(any(EzySocketDisconnectionQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setSessionTicketsQueue(any(EzySessionTicketsQueue.class)); + verify((EzyAbstractSession) session, times(1)) + .setDroppedPackets(any(EzyDroppedPackets.class)); + verify((EzyAbstractSession) session, times(1)) + .setImmediateDeliver(any(EzyImmediateDeliver.class)); + verifyNoMoreInteractions(session); + } + @Test public void addReadAndWrittenBytesFailedTest() throws Exception { // given From e0d9926de1190f6976dd05485cfd0551a327be6d Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 15 Jul 2023 22:52:39 +0700 Subject: [PATCH 31/46] update unit test --- .../nio/socket/EzyNioSocketAcceptor.java | 11 +++---- .../handler/EzyAbstractHandlerGroupTest.java | 29 +------------------ .../socket/EzyNioSocketAcceptorTest.java | 21 ++++++++++++++ 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index 6c22b192..51d976a2 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -54,11 +54,6 @@ public EzyNioSocketAcceptor( ); } - @Override - public void destroy() { - processWithLogException(ownSelector::close); - } - @Override public void handleEvent() { try { @@ -150,4 +145,10 @@ private void doAcceptConnection(SocketChannel clientChannel) throws Exception { SelectionKey selectionKey = clientChannel.register(readSelector, SelectionKey.OP_READ); session.setProperty(EzyNioSession.SELECTION_KEY, selectionKey); } + + @Override + public void destroy() { + processWithLogException(ownSelector::close); + processWithLogException(connectionAcceptorExecutorService::shutdown); + } } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index d385a44e..38cbab93 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java @@ -83,36 +83,9 @@ public void test() throws Exception { when(channel.write(any(ByteBuffer.class), anyBoolean())).thenReturn(123456); - ExHandlerGroup group = (ExHandlerGroup) new ExHandlerGroup.Builder() - .session(session) - .decoder(decoder) - .sessionCount(new AtomicInteger()) - .networkStats((EzyNetworkStats) statistics.getSocketStats().getNetworkStats()) - .sessionStats((EzySessionStats) statistics.getSocketStats().getSessionStats()) - .serverContext(serverContext) - .statsThreadPool(statsThreadPool) - .sessionTicketsRequestQueues(sessionTicketsRequestQueues) - .build(); - - EzyChannel channelX = mock(EzyChannel.class); - try { - MethodInvoker.create() - .object(group) - .method("canWriteBytes") - .param(EzyChannel.class, null) - .invoke(); - MethodInvoker.create() - .object(group) - .method("canWriteBytes") - .param(EzyChannel.class, channelX) - .invoke(); - } catch (Throwable e) { - e.printStackTrace(); - } - sessionTicketsRequestQueues = mock(EzySessionTicketsRequestQueues.class); when(sessionTicketsRequestQueues.addRequest(any())).thenReturn(false); - group = (ExHandlerGroup) new ExHandlerGroup.Builder() + ExHandlerGroup group = (ExHandlerGroup) new ExHandlerGroup.Builder() .session(session) .decoder(decoder) .sessionCount(new AtomicInteger()) diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java index 24e7c8f5..de829c5d 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java @@ -33,6 +33,7 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; +import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; @@ -84,6 +85,26 @@ public void test() throws Exception { assert acceptableConnections.size() == 0; } + @Test + public void handleEventExceptionCase() throws Exception { + // given + EzyNioSocketAcceptor instance = new EzyNioSocketAcceptor( + 1 + ); + + Selector ownSelector = mock(Selector.class); + IOException error = new IOException("test"); + when(ownSelector.select()).thenThrow(error); + instance.setOwnSelector(ownSelector); + + // when + instance.handleEvent(); + + // then + verify(ownSelector, times(1)).select(); + verifyNoMoreInteractions(ownSelector); + } + @Test public void acceptConnectionExceptionCase() throws Exception { EzyHandlerGroupManager handlerGroupManager = newHandlerGroupManager(); From 506c61e7dc74c1a5dfa8e1a97ac41092179e9055 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sun, 16 Jul 2023 15:56:38 +0700 Subject: [PATCH 32/46] update EzyHandshakeController to check clientKey != null --- .../controller/EzyHandshakeController.java | 2 +- .../EzyHandShakeControllerTest.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java index 2f2be838..aeeb51b5 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java @@ -60,7 +60,7 @@ protected void handleSocketSSL(EzyServerContext ctx, EzyHandshakeEvent event) { if (encryptedSessionKey == null) { encryptedSessionKey = sessionKey; try { - if (clientKey.length > 0) { + if (clientKey != null && clientKey.length > 0) { encryptedSessionKey = EzyAsyCrypt.builder() .publicKey(clientKey) .build() diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java index 10a8e78a..7c5bf740 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java @@ -156,6 +156,50 @@ public void handleSocketSSLButEventNoEncryptionTest() { Asserts.assertNull(session.getSessionKey()); } + @Test + public void handleSocketSSLButClientKeyNullTest() { + // given + EzyHandshakeController sut = new EzyHandshakeController(); + EzyServerContext serverContext = mock(EzyServerContext.class); + EzyHandShakeRequest request = mock(EzyHandShakeRequest.class); + + EzyHandshakeParams params = mock(EzyHandshakeParams.class); + when(request.getParams()).thenReturn(params); + + EzySession session = spy(EzyAbstractSession.class); + when(session.getConnectionType()).thenReturn(EzyConnectionType.SOCKET); + when(request.getSession()).thenReturn(session); + + EzyServer server = mock(EzyServer.class); + EzySettings settings = mock(EzySettings.class); + EzySocketSetting socketSetting = mock(EzySocketSetting.class); + when(settings.getSocket()).thenReturn(socketSetting); + when(socketSetting.isCustomizationSslActive()).thenReturn(true); + when(serverContext.getServer()).thenReturn(server); + when(server.getSettings()).thenReturn(settings); + + String clientId = RandomUtil.randomShortHexString(); + String clientType = RandomUtil.randomShortAlphabetString(); + String clientVersion = RandomUtil.randomShortAlphabetString(); + String reconnectToken = RandomUtil.randomShortHexString(); + when(params.getClientId()).thenReturn(clientId); + when(params.getClientKey()).thenReturn(null); + when(params.getClientType()).thenReturn(clientType); + when(params.getClientVersion()).thenReturn(clientVersion); + when(params.getReconnectToken()).thenReturn(reconnectToken); + when(params.isEnableEncryption()).thenReturn(true); + + // when + sut.handle(serverContext, request); + + // then + verify(session, times(1)).setClientId(clientId); + verify(session, times(1)).setClientKey(null); + verify(session, times(1)).setClientType(clientType); + verify(session, times(1)).setClientVersion(clientVersion); + verify(session, times(1)).setSessionKey(any(byte[].class)); + } + @Test public void handleSocketSSLButClientKeyEmptyTest() { // given From 7c7d0ad6b8aa9d8edf521d11cc7b3cc23e18c8e5 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 1 Aug 2023 22:41:59 +0700 Subject: [PATCH 33/46] check null --- .../java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java | 3 +++ .../tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java | 1 + 2 files changed, 4 insertions(+) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java index 8175ed7f..cbb2d5b8 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java @@ -25,6 +25,9 @@ public void setFragment(Object fragment) { @Override public int getSize() { + if (data == null) { + return 0; + } if (data instanceof String) { return ((String) data).length(); } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java index cc13192c..8a92987c 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java @@ -9,6 +9,7 @@ public class EzySimplePacketTest { @Test public void test() { EzySimplePacket packet = new EzySimplePacket(); + assert packet.getSize() == 0; packet.setData("hello"); packet.setFragment("hello"); packet.setBinary(false); From 2fb916fe9c720a95e655d82dde38d792acc238da Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 1 Aug 2023 22:52:00 +0700 Subject: [PATCH 34/46] update EzySimplePacket.getSize --- .../com/tvd12/ezyfoxserver/socket/EzySimplePacket.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java index cbb2d5b8..0b3a7de1 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java @@ -25,13 +25,14 @@ public void setFragment(Object fragment) { @Override public int getSize() { - if (data == null) { + Object currentData = data; + if (currentData == null) { return 0; } - if (data instanceof String) { - return ((String) data).length(); + if (currentData instanceof String) { + return ((String) currentData).length(); } - return ((byte[]) data).length; + return ((byte[]) currentData).length; } @Override From 6ca564b309e3f48d60da9e1d96fa21bd2f7059a6 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Wed, 2 Aug 2023 22:20:04 +0700 Subject: [PATCH 35/46] remove tryNewSslContext --- .../tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java index 04fab2a0..dc4399c3 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySimpleSslContextFactory.java @@ -24,10 +24,6 @@ public class EzySimpleSslContextFactory @Override public SSLContext newSslContext(EzySslConfig config) throws Exception { - return tryNewSslContext(config); - } - - protected SSLContext tryNewSslContext(EzySslConfig config) throws Exception { InputStream keyStoreStream = loadKeyStoreStream(config.getKeyStoreFile()); char[] keyStorePassword = getPassword(config.getKeyStorePasswordFile()); char[] certificatePassword = getPassword(config.getCertificatePasswordFile()); From f5bef64a91014541cdfc173909399ca046ddb6f4 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Wed, 2 Aug 2023 22:25:00 +0700 Subject: [PATCH 36/46] update EzySslHandshakeHandler logs --- .../com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index 1593b71e..07859547 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -77,7 +77,8 @@ public SSLEngine handle( logger.info( "A problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + - "Will try to properly close connection..." + "Will try to properly close connection...", + e ); engine.closeOutbound(); handshakeStatus = engine.getHandshakeStatus(); @@ -113,7 +114,8 @@ public SSLEngine handle( logger.info( "A problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + - "Will try to properly close connection..." + "Will try to properly close connection...", + e ); break; } From b7ecba957923029b1604c4e49f27fdb9c5aa5e90 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 5 Aug 2023 14:24:55 +0700 Subject: [PATCH 37/46] v2 --- .../api/EzyAbstractResponseApi.java | 60 +++++++++++++--- .../ezyfoxserver/api/EzyWsResponseApi.java | 4 +- .../command/impl/EzyCloseSessionImpl.java | 11 +-- .../constant/EzyDisconnectReason.java | 3 +- .../entity/EzyAbstractSession.java | 11 +-- .../setting/EzySimpleSocketSetting.java | 2 +- .../setting/EzySocketSettingBuilder.java | 2 +- .../ezyfoxserver/socket/EzySocketWriter.java | 30 ++++---- .../ssl/EzySslHandshakeHandler.java | 9 ++- .../src/main/resources/ezy-zone-settings.xml | 8 +-- .../nio/EzyNioServerBootstrap.java | 1 - .../nio/EzySocketServerBootstrap.java | 37 ++-------- .../EzyNioServerBootstrapBuilderImpl.java | 34 ++++++--- .../impl/EzyWebSocketSecureServerCreator.java | 2 +- .../nio/handler/EzyAbstractHandlerGroup.java | 5 -- .../socket/EzyNioSecureSocketAcceptor.java | 13 ++++ .../nio/socket/EzyNioSecureSocketChannel.java | 8 +-- .../nio/socket/EzyNioSocketAcceptor.java | 69 +++++-------------- .../nio/socket/EzyNioSocketReader.java | 38 ++++------ .../socket/EzySecureSocketDataReceiver.java | 40 +++++++++++ .../nio/socket/EzySocketDataReceiver.java | 2 +- .../socket/EzyNioSecureSocketChannelTest.java | 3 +- .../socket/EzyNioSocketAcceptorTest.java | 1 - 23 files changed, 217 insertions(+), 176 deletions(-) create mode 100644 ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java index 1f19d780..9e549bf8 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java @@ -3,8 +3,10 @@ import com.tvd12.ezyfox.constant.EzyConstant; import com.tvd12.ezyfox.entity.EzyArray; import com.tvd12.ezyfox.util.EzyLoggable; +import com.tvd12.ezyfoxserver.constant.EzyTransportType; import com.tvd12.ezyfoxserver.entity.EzySession; import com.tvd12.ezyfoxserver.response.EzyPackage; +import com.tvd12.ezyfoxserver.socket.EzyPacket; import com.tvd12.ezyfoxserver.socket.EzySimplePacket; import java.util.Collection; @@ -35,11 +37,16 @@ protected final void normalResponse( return; } Object bytes = encodeData(pack.getData()); + EzyConstant transportType = pack.getTransportType(); if (immediate) { for (EzySession session : recipients) { try { - Object packedBytes = packMessage(session, bytes); - session.sendNow(createPacket(packedBytes, pack)); + EzyPacket packet = createPacket( + session, + transportType, + bytes + ); + session.sendNow(packet); } catch (Throwable e) { logger.info("response data now to session: {} failed", session, e); } @@ -47,8 +54,12 @@ protected final void normalResponse( } else { for (EzySession session : recipients) { try { - Object packedBytes = packMessage(session, bytes); - session.send(createPacket(packedBytes, pack)); + EzyPacket packet = createPacket( + session, + transportType, + bytes + ); + session.send(packet); } catch (Throwable e) { logger.info("response data to session: {} failed", session, e); } @@ -66,12 +77,17 @@ protected final void secureResponse( return; } byte[] messageContent = dataToMessageContent(pack.getData()); + EzyConstant transportType = pack.getTransportType(); if (immediate) { for (EzySession session : recipients) { try { byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - Object packedBytes = packMessage(session, bytes); - session.sendNow(createPacket(packedBytes, pack)); + EzyPacket packet = createPacket( + session, + transportType, + bytes + ); + session.sendNow(packet); } catch (Throwable e) { logger.info("response data now to session: {} failed", session, e); } @@ -80,8 +96,12 @@ protected final void secureResponse( for (EzySession session : recipients) { try { byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - Object packedBytes = packMessage(session, bytes); - session.send(createPacket(packedBytes, pack)); + EzyPacket packet = createPacket( + session, + transportType, + bytes + ); + session.send(packet); } catch (Throwable e) { logger.info("response data to session: {} failed", session, e); } @@ -89,9 +109,29 @@ protected final void secureResponse( } } - protected EzySimplePacket createPacket(Object bytes, EzyPackage pack) { + private EzyPacket createPacket( + EzySession session, + EzyConstant transportType, + Object bytes + ) throws Exception { + EzyConstant actualTransportType = transportType; + if (actualTransportType == EzyTransportType.UDP_OR_TCP) { + actualTransportType = session.getDatagramChannelPool() != null + ? EzyTransportType.UDP + : EzyTransportType.TCP; + } + Object packedBytes = actualTransportType == EzyTransportType.UDP + ? bytes + : packMessage(session, bytes); + return createPacket(actualTransportType, packedBytes); + } + + protected EzySimplePacket createPacket( + EzyConstant transportType, + Object bytes + ) { EzySimplePacket packet = new EzySimplePacket(); - packet.setTransportType(pack.getTransportType()); + packet.setTransportType(transportType); packet.setData(bytes); return packet; } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyWsResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyWsResponseApi.java index ea1e8ad3..928b895e 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyWsResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyWsResponseApi.java @@ -23,8 +23,8 @@ public void response(EzyPackage pack, boolean immediate) throws Exception { } @Override - protected EzySimplePacket createPacket(Object bytes, EzyPackage pack) { - EzySimplePacket packet = super.createPacket(bytes, pack); + protected EzySimplePacket createPacket(EzyConstant transportType, Object bytes) { + EzySimplePacket packet = super.createPacket(transportType, bytes); packet.setBinary(false); return packet; } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/command/impl/EzyCloseSessionImpl.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/command/impl/EzyCloseSessionImpl.java index 98da8e19..45fdf688 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/command/impl/EzyCloseSessionImpl.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/command/impl/EzyCloseSessionImpl.java @@ -22,18 +22,19 @@ public EzyCloseSessionImpl(EzyServerContext ctx) { @Override public void close(EzySession session, EzyConstant reason) { - sendToClients(session, reason); + sendToClient(session, reason); disconnectSession(session, reason); } - protected void sendToClients(EzySession session, EzyConstant reason) { + protected void sendToClient(EzySession session, EzyConstant reason) { if (shouldSendToClient(reason)) { - doSendToClients(session, reason); + doSendToClient(session, reason); } } protected boolean shouldSendToClient(EzyConstant reason) { - return reason != EzyDisconnectReason.UNKNOWN; + return reason != EzyDisconnectReason.UNKNOWN + && reason != EzyDisconnectReason.SSH_HANDSHAKE_FAILED; } protected void disconnectSession(EzySession session, EzyConstant reason) { @@ -41,7 +42,7 @@ protected void disconnectSession(EzySession session, EzyConstant reason) { session.close(); } - protected void doSendToClients(EzySession session, EzyConstant reason) { + protected void doSendToClient(EzySession session, EzyConstant reason) { EzyResponse response = newResponse(reason); context.sendNow(response, session); } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyDisconnectReason.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyDisconnectReason.java index 6cc9def6..c48c1c20 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyDisconnectReason.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyDisconnectReason.java @@ -16,7 +16,8 @@ public enum EzyDisconnectReason implements EzyConstant { ADMIN_KICK(5), MAX_REQUEST_PER_SECOND(6), MAX_REQUEST_SIZE(7), - SERVER_ERROR(8); + SERVER_ERROR(8), + SSH_HANDSHAKE_FAILED(9); private static final Map REASONS_BY_ID = reasonsById(); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java index 4d57562f..d180fabb 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java @@ -86,9 +86,9 @@ public abstract class EzyAbstractSession @Setter(AccessLevel.NONE) protected volatile boolean disconnectionRegistered; @Setter(AccessLevel.NONE) - protected Object disconnectionLock = new Object(); + protected final Object disconnectionLock = new Object(); @Setter(AccessLevel.NONE) - protected Map locks = new ConcurrentHashMap<>(); + protected final Map locks = new ConcurrentHashMap<>(); public void setOwner(EzyUser owner) { this.ownerName = owner.getName(); @@ -180,7 +180,6 @@ private void addPacketToSessionQueue(EzyPacket packet) { } } - @SuppressWarnings("SynchronizeOnNonFinalField") @Override public void disconnect(EzyConstant disconnectReason) { synchronized (disconnectionLock) { @@ -236,12 +235,8 @@ public void destroy() { this.readBytes = 0L; this.writtenBytes = 0L; this.connectionType = null; - this.disconnectionLock = null; - if (locks != null) { - this.locks.clear(); - } + this.locks.clear(); this.properties.clear(); - this.locks = null; this.droppedPackets = null; this.immediateDeliver = null; if (packetQueue != null) { diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java index b9e2ece8..854155f8 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java @@ -45,7 +45,7 @@ public EzySimpleSocketSetting() { super(); setPort(3005); setSslType(SslType.CUSTOMIZATION); - setSslHandshakeTimeout(300); + setSslHandshakeTimeout(350); setMaxRequestSize(4096); setConnectionAcceptorThreadPoolSize(1); setSslConnectionAcceptorThreadPoolSize(8); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java index 946c8721..cba3c9f5 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java @@ -18,7 +18,7 @@ public class EzySocketSettingBuilder extends public EzySocketSettingBuilder() { this.port = 3005; this.sslType = SslType.CUSTOMIZATION; - this.sslHandshakeTimeout = 300; + this.sslHandshakeTimeout = 350; this.maxRequestSize = 32768; this.connectionAcceptorThreadPoolSize = 1; this.sslConnectionAcceptorThreadPoolSize = 8; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java index 21df4219..b315d8e1 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketWriter.java @@ -16,15 +16,6 @@ public class EzySocketWriter @Override public void handleEvent() { - doProcessSessionTicketsQueue(); - } - - @Override - public void destroy() { - processWithLogException(() -> sessionTicketsQueue.clear()); - } - - private void doProcessSessionTicketsQueue() { try { EzySession session = sessionTicketsQueue.take(); processSessionQueue(session); @@ -35,8 +26,16 @@ private void doProcessSessionTicketsQueue() { } } - private void processSessionQueue(EzySession session) throws Exception { - EzySocketWriterGroup group = getWriterGroup(session); + @Override + public void destroy() { + processWithLogException(() -> sessionTicketsQueue.clear()); + } + + private void processSessionQueue( + EzySession session + ) throws Exception { + EzySocketWriterGroup group = writerGroupFetcher + .getWriterGroup(session); if (group == null) { return; } @@ -50,7 +49,10 @@ private void processSessionQueue(EzySession session) throws Exception { } } - private boolean processSessionQueue(EzySocketWriterGroup group, EzyPacketQueue queue) + private boolean processSessionQueue( + EzySocketWriterGroup group, + EzyPacketQueue queue + ) throws Exception { if (!queue.isEmpty()) { EzyPacket packet = queue.peek(); @@ -67,8 +69,4 @@ private boolean processSessionQueue(EzySocketWriterGroup group, EzyPacketQueue q protected Object getWriteBuffer() { return null; } - - protected EzySocketWriterGroup getWriterGroup(EzySession session) { - return writerGroupFetcher.getWriterGroup(session); - } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index 07859547..0e0864ba 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -61,7 +61,8 @@ public SSLEngine handle( logger.info( "This engine was forced to close inbound, " + "without having received the proper SSL/TLS close " + - "notification message from the peer, due to end of stream." + "notification message from the peer, due to end of stream.", + e ); } engine.closeOutbound(); @@ -132,7 +133,8 @@ public SSLEngine handle( } catch (Exception e) { logger.info( "Failed to send server's close message " + - "due to socket channel's failure." + "due to socket channel's failure.", + e ); handshakeStatus = engine.getHandshakeStatus(); } @@ -151,6 +153,9 @@ public SSLEngine handle( break; } } + if (handshakeStatus == NOT_HANDSHAKING) { + throw new SSLException("not handshaking"); + } return engine; } } diff --git a/ezyfox-server-core/src/main/resources/ezy-zone-settings.xml b/ezyfox-server-core/src/main/resources/ezy-zone-settings.xml index 56e8c191..556c84e2 100644 --- a/ezyfox-server-core/src/main/resources/ezy-zone-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-zone-settings.xml @@ -10,7 +10,7 @@ true Guest# - 15000 + 15 true 15 ^[a-zA-Z0-9_.#]{3,64}$ @@ -32,7 +32,7 @@ ezyfox-chat com.tvd12.ezyfoxserver.chat.EzyChatEntryLoader 10000 - 30 + 1 config.properties @@ -42,7 +42,7 @@ ezyfox-auth-plugin -1 com.tvd12.ezyfoxserver.plugin.auth.EzyAuthPluginEntryLoader - 30 + 1 config.properties USER_LOGIN @@ -53,4 +53,4 @@ - + diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java index 72f53c0c..e36d2e98 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java @@ -130,7 +130,6 @@ private void startUserRemovalHandlingLoopHandlers() throws Exception { private EzySocketServerBootstrap newSocketServerBootstrap() { return EzySocketServerBootstrap.builder() .serverContext(context) - .sslContext(sslContext) .socketDataReceiver(socketDataReceiver) .handlerGroupManager(handlerGroupManager) .sessionTicketsQueue(socketSessionTicketsQueue) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 3ecd890f..73cd368f 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -7,9 +7,7 @@ import com.tvd12.ezyfoxserver.socket.EzySocketEventLoopOneHandler; import com.tvd12.ezyfoxserver.socket.EzySocketWriter; import com.tvd12.ezyfoxserver.socket.EzySocketWritingLoopHandler; -import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; -import javax.net.ssl.SSLContext; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; @@ -26,12 +24,10 @@ public class EzySocketServerBootstrap extends EzyAbstractSocketServerBootstrap { private ServerSocketChannel serverSocketChannel; private EzySocketEventLoopHandler readingLoopHandler; private EzySocketEventLoopHandler socketAcceptanceLoopHandler; - private final SSLContext sslContext; private final EzySocketSetting socketSetting; public EzySocketServerBootstrap(Builder builder) { super(builder); - this.sslContext = builder.sslContext; this.socketSetting = serverSettings.getSocket(); } @@ -89,23 +85,9 @@ private void startSocketHandlers() throws Exception { } private EzyNioSocketAcceptor newSocketAcceptor() { - EzyNioSocketAcceptor acceptor; - if (socketSetting.isCertificationSslActive()) { - acceptor = new EzyNioSocketAcceptor( - socketSetting.getSslConnectionAcceptorThreadPoolSize() - ); - acceptor.setSslHandshakeHandler( - new EzySslHandshakeHandler( - sslContext, - socketSetting.getSslHandshakeTimeout() - ) - ); - } else { - acceptor = new EzyNioSocketAcceptor( - socketSetting.getConnectionAcceptorThreadPoolSize() - ); - } - return acceptor; + return socketSetting.isCertificationSslActive() + ? new EzyNioSecureSocketAcceptor() + : new EzyNioSocketAcceptor(); } private EzySocketEventLoopHandler newWritingLoopHandler() { @@ -161,15 +143,10 @@ private int getSocketAcceptorThreadPoolSize() { return EzyNioThreadPoolSizes.SOCKET_ACCEPTOR; } - public static class Builder - extends EzyAbstractSocketServerBootstrap.Builder { - - private SSLContext sslContext; - - public Builder sslContext(SSLContext sslContext) { - this.sslContext = sslContext; - return this; - } + public static class Builder extends EzyAbstractSocketServerBootstrap.Builder< + Builder, + EzySocketServerBootstrap + > { @Override public EzySocketServerBootstrap build() { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index f7d4355c..420f0ab3 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -18,7 +18,9 @@ import com.tvd12.ezyfoxserver.nio.wrapper.impl.EzyHandlerGroupManagerImpl; import com.tvd12.ezyfoxserver.setting.EzySocketSetting; import com.tvd12.ezyfoxserver.socket.*; +import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; +import javax.net.ssl.SSLContext; import java.util.concurrent.ExecutorService; public class EzyNioServerBootstrapBuilderImpl @@ -43,10 +45,16 @@ protected EzyServerBootstrap newServerBootstrap() { socketDisconnectionQueue, socketSessionTicketsQueue, websocketSessionTicketsQueue, - sessionTicketsRequestQueues); + sessionTicketsRequestQueues + ); EzyHandlerGroupManager handlerGroupManager = newHandlerGroupManager( - handlerGroupBuilderFactory); - EzySocketDataReceiver socketDataReceiver = newSocketDataReceiver(handlerGroupManager); + handlerGroupBuilderFactory + ); + SSLContext sslContext = newSslContext(getWebsocketSetting().getSslConfig()); + EzySocketDataReceiver socketDataReceiver = newSocketDataReceiver( + handlerGroupManager, + sslContext + ); EzyNioServerBootstrap bootstrap = new EzyNioServerBootstrap(); bootstrap.setResponseApi(responseApi); bootstrap.setStreamingApi(streamingApi); @@ -57,7 +65,7 @@ protected EzyServerBootstrap newServerBootstrap() { bootstrap.setSocketSessionTicketsQueue(socketSessionTicketsQueue); bootstrap.setWebsocketSessionTicketsQueue(websocketSessionTicketsQueue); bootstrap.setSocketSessionTicketsRequestQueues(sessionTicketsRequestQueues); - bootstrap.setSslContext(newSslContext(getWebsocketSetting().getSslConfig())); + bootstrap.setSslContext(sslContext); return bootstrap; } @@ -105,18 +113,28 @@ private ExecutorService newStatsThreadPool() { } private EzySocketDataReceiver newSocketDataReceiver( - EzyHandlerGroupManager handlerGroupManager + EzyHandlerGroupManager handlerGroupManager, + SSLContext sslContext ) { - return newSocketDataReceiverBuilder() + return newSocketDataReceiverBuilder(sslContext) .handlerGroupManager(handlerGroupManager) .threadPoolSize(getThreadPoolSizeSetting().getSocketDataReceiver()) .build(); } - private EzySocketDataReceiver.Builder newSocketDataReceiverBuilder() { + private EzySocketDataReceiver.Builder newSocketDataReceiverBuilder( + SSLContext sslContext + ) { EzySocketSetting setting = getSocketSetting(); return setting.isCertificationSslActive() - ? EzySecureSocketDataReceiver.builder() + ? EzySecureSocketDataReceiver + .builder() + .sslHandshakeHandler( + new EzySslHandshakeHandler( + sslContext, + setting.getSslHandshakeTimeout() + ) + ) : EzySocketDataReceiver.builder(); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyWebSocketSecureServerCreator.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyWebSocketSecureServerCreator.java index 74190fa6..dbd0df21 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyWebSocketSecureServerCreator.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyWebSocketSecureServerCreator.java @@ -8,7 +8,7 @@ public class EzyWebSocketSecureServerCreator extends EzyWebSocketServerCreator { - protected SSLContext sslContext; + protected final SSLContext sslContext; public EzyWebSocketSecureServerCreator(SSLContext sslContext) { this.sslContext = sslContext; diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java index 083d5337..d9502f5b 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java @@ -173,11 +173,6 @@ protected final void executeSendingPacket(EzyPacket packet, Object writeBuffer) try { if (session.isActivated() && channel.isConnected()) { EzyConstant transportType = packet.getTransportType(); - if (transportType == EzyTransportType.UDP_OR_TCP) { - transportType = session.getDatagramChannelPool() != null - ? EzyTransportType.UDP - : EzyTransportType.TCP; - } int writeBytes = transportType == EzyTransportType.TCP ? writePacketToSocket(packet, writeBuffer) : writeUdpPacketToSocket(packet, writeBuffer); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java new file mode 100644 index 00000000..a5b0cff0 --- /dev/null +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java @@ -0,0 +1,13 @@ +package com.tvd12.ezyfoxserver.nio.socket; + +import com.tvd12.ezyfoxserver.socket.EzyChannel; + +import java.nio.channels.SocketChannel; + +public class EzyNioSecureSocketAcceptor extends EzyNioSocketAcceptor { + + @Override + protected EzyChannel newChannel(SocketChannel clientChannel) { + return new EzyNioSecureSocketChannel(clientChannel); + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index d1dc6b74..1fd5ae90 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -2,6 +2,7 @@ import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import lombok.Getter; +import lombok.Setter; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -15,15 +16,14 @@ public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { + @Setter @Getter - private final SSLEngine engine; + private SSLEngine engine; public EzyNioSecureSocketChannel( - SocketChannel channel, - SSLEngine engine + SocketChannel channel ) { super(channel); - this.engine = engine; } @Override diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java index 51d976a2..2ff8073b 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketAcceptor.java @@ -1,6 +1,5 @@ package com.tvd12.ezyfoxserver.nio.socket; -import com.tvd12.ezyfox.concurrent.EzyExecutors; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.nio.entity.EzyNioSession; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; @@ -8,10 +7,8 @@ import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManagerAware; import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.ezyfoxserver.socket.EzySocketAbstractEventHandler; -import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import lombok.Setter; -import javax.net.ssl.SSLEngine; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -20,8 +17,6 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; @@ -37,22 +32,10 @@ public class EzyNioSocketAcceptor protected Selector readSelector; @Setter protected EzyHandlerGroupManager handlerGroupManager; - @Setter - protected EzySslHandshakeHandler sslHandshakeHandler; - protected final List acceptableConnections; - protected final List acceptableConnectionsBuffer; - protected final ExecutorService connectionAcceptorExecutorService; - - public EzyNioSocketAcceptor( - int connectionAcceptorThreadPoolSize - ) { - acceptableConnections = new ArrayList<>(); - acceptableConnectionsBuffer = new ArrayList<>(); - connectionAcceptorExecutorService = EzyExecutors.newFixedThreadPool( - connectionAcceptorThreadPoolSize, - "connection-acceptor" - ); - } + protected final List acceptableConnections = + new ArrayList<>(); + protected final List acceptableConnectionsBuffer = + new ArrayList<>(); @Override public void handleEvent() { @@ -90,25 +73,15 @@ private void addConnection(SocketChannel clientChannel) { } } - public void handleAcceptableConnections() throws Exception { + public void handleAcceptableConnections() { synchronized (acceptableConnections) { acceptableConnectionsBuffer.addAll(acceptableConnections); acceptableConnections.clear(); } - CountDownLatch countDownLatch = new CountDownLatch( - acceptableConnectionsBuffer.size() - ); try { for (SocketChannel clientChannel : acceptableConnectionsBuffer) { - connectionAcceptorExecutorService.execute(() -> { - try { - acceptConnection(clientChannel); - } finally { - countDownLatch.countDown(); - } - }); + acceptConnection(clientChannel); } - countDownLatch.await(); } finally { acceptableConnectionsBuffer.clear(); } @@ -122,33 +95,29 @@ private void acceptConnection(SocketChannel clientChannel) { } } - private void doAcceptConnection(SocketChannel clientChannel) throws Exception { + private void doAcceptConnection( + SocketChannel clientChannel + ) throws Exception { clientChannel.configureBlocking(false); clientChannel.socket().setTcpNoDelay(tcpNoDelay); - SSLEngine sslEngine = null; - try { - if (sslHandshakeHandler != null) { - sslEngine = sslHandshakeHandler.handle(clientChannel); - } - } catch (Exception e) { - clientChannel.close(); - throw e; - } - EzyChannel channel = sslEngine == null - ? new EzyNioSocketChannel(clientChannel) - : new EzyNioSecureSocketChannel(clientChannel, sslEngine); - + SelectionKey selectionKey = clientChannel + .register(readSelector, SelectionKey.OP_READ); + EzyChannel channel = newChannel(clientChannel); EzyNioHandlerGroup handlerGroup = handlerGroupManager .newHandlerGroup(channel, EzyConnectionType.SOCKET); EzyNioSession session = handlerGroup.getSession(); - - SelectionKey selectionKey = clientChannel.register(readSelector, SelectionKey.OP_READ); session.setProperty(EzyNioSession.SELECTION_KEY, selectionKey); } + protected EzyChannel newChannel(SocketChannel clientChannel) { + return new EzyNioSocketChannel(clientChannel); + } + @Override public void destroy() { + synchronized (acceptableConnections) { + acceptableConnections.clear(); + } processWithLogException(ownSelector::close); - processWithLogException(connectionAcceptorExecutorService::shutdown); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java index 5da66655..e9be2113 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java @@ -28,51 +28,43 @@ public void destroy() { @Override public void handleEvent() { try { - handleAcceptableConnections(); - doProcessReadyKeys(); + acceptableConnectionsHandler.handleAcceptableConnections(); + int readyKeyCount = ownSelector.selectNow(); + if (readyKeyCount > 0) { + processReadyKeys(); + } Thread.sleep(3L); } catch (Throwable e) { logger.info("I/O error at socket-reader", e); } } - private void handleAcceptableConnections() throws Exception { - acceptableConnectionsHandler.handleAcceptableConnections(); - } - - private void doProcessReadyKeys() throws Exception { - int readyKeyCount = ownSelector.selectNow(); - if (readyKeyCount > 0) { - processReadyKeys(); - } - } - - private void processReadyKeys() { + protected void processReadyKeys() { Set readyKeys = this.ownSelector.selectedKeys(); Iterator iterator = readyKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); iterator.remove(); if (key.isValid()) { - processReadyKey(key); + try { + processReadyKey(key); + } catch (Throwable e) { + logger.info("process ready key: {} error", key, e); + } } } } private void processReadyKey(SelectionKey key) { if (key.isWritable()) { - processWritableKey(key); + key.interestOps(SelectionKey.OP_READ); } if (key.isReadable()) { - processReadableKey(key); + processReadableChannel((SocketChannel) key.channel()); } } - private void processWritableKey(SelectionKey key) { - key.interestOps(SelectionKey.OP_READ); - } - - private void processReadableKey(SelectionKey key) { - socketDataReceiver.tcpReceive((SocketChannel) key.channel()); + protected void processReadableChannel(SocketChannel channel) { + socketDataReceiver.tcpReceive(channel); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 19c7ede3..153b4bb3 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -1,12 +1,16 @@ package com.tvd12.ezyfoxserver.nio.socket; +import com.tvd12.ezyfoxserver.constant.EzyDisconnectReason; import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.socket.EzyChannel; +import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLSession; import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; @@ -15,12 +19,41 @@ public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { protected final ByteBuffer[] tcpNetBuffers; + protected final EzySslHandshakeHandler sslHandshakeHandler; public EzySecureSocketDataReceiver(Builder builder) { super(builder); + this.sslHandshakeHandler = builder.sslHandshakeHandler; this.tcpNetBuffers = newTcpByteBuffers(threadPoolSize); } + @Override + protected void tcpReadBytes( + SocketChannel channel, + ByteBuffer buffer + ) throws Throwable { + EzyNioHandlerGroup handlerGroup = + handlerGroupManager.getHandlerGroup(channel); + if (handlerGroup != null) { + EzyNioSecureSocketChannel secureChannel = + (EzyNioSecureSocketChannel) handlerGroup.getChannel(); + if (secureChannel.getEngine() == null) { + try { + secureChannel.setEngine( + sslHandshakeHandler.handle(channel) + ); + } catch (Exception e) { + logger.info("handshake failed on channel: {}", channel, e); + handlerGroup.enqueueDisconnection( + EzyDisconnectReason.SSH_HANDSHAKE_FAILED + ); + return; + } + } + } + super.tcpReadBytes(channel, buffer); + } + @Override protected byte[] readTcpBytesFromBuffer( EzyChannel channel, @@ -66,6 +99,13 @@ public static Builder builder() { public static class Builder extends EzySocketDataReceiver.Builder { + protected EzySslHandshakeHandler sslHandshakeHandler; + + public Builder sslHandshakeHandler(EzySslHandshakeHandler sslHandshakeHandler) { + this.sslHandshakeHandler = sslHandshakeHandler; + return this; + } + @Override public EzySocketDataReceiver build() { return new EzySecureSocketDataReceiver(this); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java index f1b7bd97..37e73932 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java @@ -73,7 +73,7 @@ private void doTcpReceive(SocketChannel channel, ByteBuffer buffer) { } } - private void tcpReadBytes( + protected void tcpReadBytes( SocketChannel channel, ByteBuffer buffer ) throws Throwable { diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java index f75763fa..1507dedd 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java @@ -35,8 +35,7 @@ public void setup() { sslSession = mock(SSLSession.class); socketChannel = mock(SocketChannel.class); instance = new EzyNioSecureSocketChannel( - socketChannel, - sslEngine + socketChannel ); when(sslEngine.getSession()).thenReturn(sslSession); when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java index de829c5d..bf980ae5 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java @@ -240,7 +240,6 @@ public void acceptConnectionSslTest() throws Exception { SSLEngine sslEngine = mock(SSLEngine.class); when(sslHandshakeHandler.handle(clientChannel)) .thenReturn(sslEngine); - sut.setSslHandshakeHandler(sslHandshakeHandler); // when MethodInvoker.create() From b00b3dcf88f189423a877b1c77e8d9efcb160ca1 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sun, 6 Aug 2023 08:47:08 +0700 Subject: [PATCH 38/46] update --- .../constant/EzyCoreConstants.java | 1 + .../nio/handler/EzyAbstractHandlerGroup.java | 5 ++- .../nio/handler/EzySimpleNioHandlerGroup.java | 22 +++---------- .../nio/socket/EzyNioSecureSocketChannel.java | 20 +++++++++-- .../socket/EzySecureSocketDataReceiver.java | 33 +++++++++++-------- .../nio/socket/EzySocketDataReceiver.java | 10 +++--- 6 files changed, 50 insertions(+), 41 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java index 8521e860..d8de98d1 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java @@ -3,6 +3,7 @@ public final class EzyCoreConstants { public static final int MAX_READ_BUFFER_SIZE = 4096; + public static final int MAX_SECURE_READ_BUFFER_SIZE = 16709; private EzyCoreConstants() {} } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java index d9502f5b..5575d01f 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzyAbstractHandlerGroup.java @@ -183,11 +183,10 @@ protected final void executeSendingPacket(EzyPacket packet, Object writeBuffer) networkStats.addWriteErrorPackets(1); networkStats.addWriteErrorBytes(packetSize); logger.info( - "can't send {} bytes to session: {}, error: {}({})", + "can't send {} bytes to session: {}", packetSize, session, - e.getClass().getName(), - e.getMessage() + e ); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzySimpleNioHandlerGroup.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzySimpleNioHandlerGroup.java index 19f5fda2..7dc92ee1 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzySimpleNioHandlerGroup.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/handler/EzySimpleNioHandlerGroup.java @@ -31,15 +31,6 @@ protected EzyMessageDataDecoder newDecoder(Object decoder) { @Override public void fireBytesReceived(byte[] bytes) { - handleReceivedBytes(bytes); - } - - @Override - public void fireMessageReceived(EzyMessage message) { - handleReceivedMessage(message); - } - - private void handleReceivedBytes(byte[] bytes) { try { decoder.decode(bytes, decodeBytesCallback); } catch (Throwable throwable) { @@ -47,11 +38,12 @@ private void handleReceivedBytes(byte[] bytes) { } } - private void executeHandleReceivedMessage(EzyMessage message) { - handleReceivedMessage(message); + @Override + public void fireMessageReceived(EzyMessage message) { + executeHandleReceivedMessage(message); } - private void handleReceivedMessage(EzyMessage message) { + private void executeHandleReceivedMessage(EzyMessage message) { try { EzyMessageHeader header = message.getHeader(); if (header.isRawBytes()) { @@ -63,7 +55,7 @@ private void handleReceivedMessage(EzyMessage message) { EzySocketStream stream = new EzySimpleSocketStream(session, rawBytes); streamQueue.add(stream); } else { - Object data = decodeMessage(message); + Object data = decoder.decode(message, session.getSessionKey()); int dataSize = message.getByteCount(); handleReceivedData(data, dataSize); } @@ -74,10 +66,6 @@ private void handleReceivedMessage(EzyMessage message) { } } - private Object decodeMessage(EzyMessage message) throws Exception { - return decoder.decode(message, session.getSessionKey()); - } - @Override protected void doSendPacketNow(EzyPacket packet) { ByteBuffer writeBuffer = ByteBuffer.allocate(packet.getSize()); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 1fd5ae90..eb5ebbc8 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -2,7 +2,6 @@ import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import lombok.Getter; -import lombok.Setter; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -16,9 +15,11 @@ public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { - @Setter @Getter private SSLEngine engine; + private SSLSession session; + @Getter + private ByteBuffer readAppBuffer; public EzyNioSecureSocketChannel( SocketChannel channel @@ -26,10 +27,15 @@ public EzyNioSecureSocketChannel( super(channel); } + public void setEngine(SSLEngine engine) { + this.engine = engine; + this.session = engine.getSession(); + this.readAppBuffer = ByteBuffer.allocate(session.getApplicationBufferSize()); + } + @Override public byte[] pack(byte[] bytes) throws Exception { ByteBuffer buffer = ByteBuffer.wrap(bytes); - SSLSession session = engine.getSession(); int netBufferLength = session.getPacketBufferSize(); ByteBuffer netBuffer = ByteBuffer.allocate(netBufferLength); while (buffer.hasRemaining()) { @@ -57,4 +63,12 @@ public byte[] pack(byte[] bytes) throws Exception { } return bytes; } + + @Override + public void close() { + super.close(); + this.engine = null; + this.readAppBuffer = null; + + } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 153b4bb3..86fb3b0b 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -12,19 +12,18 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import static com.tvd12.ezyfoxserver.constant.EzyCoreConstants.MAX_SECURE_READ_BUFFER_SIZE; import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { - protected final ByteBuffer[] tcpNetBuffers; protected final EzySslHandshakeHandler sslHandshakeHandler; public EzySecureSocketDataReceiver(Builder builder) { super(builder); this.sslHandshakeHandler = builder.sslHandshakeHandler; - this.tcpNetBuffers = newTcpByteBuffers(threadPoolSize); } @Override @@ -65,18 +64,21 @@ protected byte[] readTcpBytesFromBuffer( SSLSession session = engine.getSession(); int appBufferSize = session.getApplicationBufferSize(); int packageBufferSize = session.getPacketBufferSize(); - ByteBuffer appBuffer = buffer; - int index = Math.abs(channel.hashCode() % threadPoolSize); - ByteBuffer tcpNetBuffer = tcpNetBuffers[index]; - while (appBuffer.hasRemaining()) { - tcpNetBuffer.clear(); - SSLEngineResult result = engine.unwrap(appBuffer, tcpNetBuffer); + ByteBuffer netBuffer = secureChannel.getReadAppBuffer(); + if (netBuffer.position() > 0) { + netBuffer.compact(); + } + netBuffer.put(buffer); + netBuffer.flip(); + ByteBuffer tcpAppBuffer = ByteBuffer.allocate(appBufferSize); + while (netBuffer.hasRemaining()) { + SSLEngineResult result = engine.unwrap(netBuffer, tcpAppBuffer); switch (result.getStatus()) { case BUFFER_OVERFLOW: - appBuffer = enlargeBuffer(appBuffer, appBufferSize); + netBuffer = enlargeBuffer(netBuffer, appBufferSize); break; case BUFFER_UNDERFLOW: - tcpNetBuffer = enlargeBufferIfNeed(tcpNetBuffer, packageBufferSize); + tcpAppBuffer = enlargeBufferIfNeed(tcpAppBuffer, packageBufferSize); break; case CLOSED: safeCloseOutbound(engine); @@ -84,15 +86,20 @@ protected byte[] readTcpBytesFromBuffer( "ssl unwrap result status is CLOSE" ); default: // 0K - tcpNetBuffer.flip(); - byte[] binary = new byte[tcpNetBuffer.limit()]; - tcpNetBuffer.get(binary); + tcpAppBuffer.flip(); + byte[] binary = new byte[tcpAppBuffer.limit()]; + tcpAppBuffer.get(binary); return binary; } } return new byte[0]; } + @Override + protected int getMaxBufferSize() { + return MAX_SECURE_READ_BUFFER_SIZE; + } + public static Builder builder() { return new Builder(); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java index 37e73932..97abcc92 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java @@ -35,6 +35,10 @@ public EzySocketDataReceiver(Builder builder) { this.executorServices = newExecutorServices(threadPoolSize); } + public static Builder builder() { + return new Builder(); + } + protected ByteBuffer[] newTcpByteBuffers(int size) { ByteBuffer[] answer = new ByteBuffer[size]; for (int i = 0; i < size; ++i) { @@ -214,14 +218,10 @@ private ExecutorService selectedExecutorService(Object channel) { return executorServices[index]; } - private int getMaxBufferSize() { + protected int getMaxBufferSize() { return EzyCoreConstants.MAX_READ_BUFFER_SIZE; } - public static Builder builder() { - return new Builder(); - } - public static class Builder implements EzyBuilder { protected int threadPoolSize; protected EzyHandlerGroupManager handlerGroupManager; From 3376947aaf6bbbbeb8992ed0551d4fdc9ccd4fc6 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sun, 6 Aug 2023 23:03:57 +0700 Subject: [PATCH 39/46] runnable --- .../api/EzyAbstractResponseApi.java | 58 ++++++++++++++----- .../ezyfoxserver/api/EzyProxyResponseApi.java | 8 ++- .../api/EzySecureProxyResponseApi.java | 17 ++++++ .../api/EzySecureSocketResponseApi.java | 55 ++++++++++++++++++ .../api/EzySocketResponseApi.java | 20 ------- .../tvd12/ezyfoxserver/socket/EzyPacket.java | 2 + .../ezyfoxserver/socket/EzySimplePacket.java | 5 ++ .../ssl/EzySslHandshakeHandler.java | 2 +- .../EzyNioServerBootstrapBuilderImpl.java | 10 ++-- .../nio/socket/EzyNioSecureSocketChannel.java | 21 ++++++- .../socket/EzySecureSocketDataReceiver.java | 22 ++++++- 11 files changed, 174 insertions(+), 46 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureProxyResponseApi.java create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java index 9e549bf8..c1578a60 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java @@ -46,7 +46,7 @@ protected final void normalResponse( transportType, bytes ); - session.sendNow(packet); + sendPacketNow(session, packet); } catch (Throwable e) { logger.info("response data now to session: {} failed", session, e); } @@ -59,7 +59,7 @@ protected final void normalResponse( transportType, bytes ); - session.send(packet); + sendPacket(session, packet); } catch (Throwable e) { logger.info("response data to session: {} failed", session, e); } @@ -87,7 +87,7 @@ protected final void secureResponse( transportType, bytes ); - session.sendNow(packet); + sendPacketNow(session, packet); } catch (Throwable e) { logger.info("response data now to session: {} failed", session, e); } @@ -101,7 +101,7 @@ protected final void secureResponse( transportType, bytes ); - session.send(packet); + sendPacket(session, packet); } catch (Throwable e) { logger.info("response data to session: {} failed", session, e); } @@ -113,17 +113,14 @@ private EzyPacket createPacket( EzySession session, EzyConstant transportType, Object bytes - ) throws Exception { + ) { EzyConstant actualTransportType = transportType; if (actualTransportType == EzyTransportType.UDP_OR_TCP) { actualTransportType = session.getDatagramChannelPool() != null ? EzyTransportType.UDP : EzyTransportType.TCP; } - Object packedBytes = actualTransportType == EzyTransportType.UDP - ? bytes - : packMessage(session, bytes); - return createPacket(actualTransportType, packedBytes); + return createPacket(actualTransportType, bytes); } protected EzySimplePacket createPacket( @@ -136,6 +133,42 @@ protected EzySimplePacket createPacket( return packet; } + private void sendPacket( + EzySession session, + EzyPacket packet + ) throws Exception { + if (packet.getTransportType() == EzyTransportType.UDP) { + session.send(packet); + } else { + sendTcpPacket(session, packet); + } + } + + protected void sendTcpPacket( + EzySession session, + EzyPacket packet + ) throws Exception { + session.send(packet); + } + + private void sendPacketNow( + EzySession session, + EzyPacket packet + ) throws Exception { + if (packet.getTransportType() == EzyTransportType.UDP) { + session.sendNow(packet); + } else { + sendTcpPacketNow(session, packet); + } + } + + protected void sendTcpPacketNow( + EzySession session, + EzyPacket packet + ) throws Exception { + session.sendNow(packet); + } + protected abstract EzyConstant getConnectionType(); protected abstract Object encodeData(EzyArray data) throws Exception; @@ -150,11 +183,4 @@ protected byte[] encryptMessageContent( ) throws Exception { throw new UnsupportedOperationException("unsupported"); } - - protected Object packMessage( - EzySession session, - Object message - ) throws Exception { - return message; - } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyProxyResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyProxyResponseApi.java index 2f1fa912..5f4d364f 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyProxyResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyProxyResponseApi.java @@ -18,10 +18,16 @@ public EzyProxyResponseApi(EzyCodecFactory codecFactory) { private EzyResponseApi newSocketResponseApi(Object socketEncoder) { return socketEncoder != null - ? new EzySocketResponseApi(socketEncoder) + ? createSocketResponseApi(socketEncoder) : EzyEmptyResponseApi.getInstance(); } + protected EzySocketResponseApi createSocketResponseApi( + Object socketEncoder + ) { + return new EzySocketResponseApi(socketEncoder); + } + private EzyResponseApi newWebsocketResponseApi(Object wsEncoder) { return wsEncoder != null ? new EzyWsResponseApi(wsEncoder) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureProxyResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureProxyResponseApi.java new file mode 100644 index 00000000..7e972a50 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureProxyResponseApi.java @@ -0,0 +1,17 @@ +package com.tvd12.ezyfoxserver.api; + +import com.tvd12.ezyfoxserver.codec.EzyCodecFactory; + +public class EzySecureProxyResponseApi extends EzyProxyResponseApi { + + public EzySecureProxyResponseApi(EzyCodecFactory codecFactory) { + super(codecFactory); + } + + @Override + protected EzySocketResponseApi createSocketResponseApi( + Object socketEncoder + ) { + return new EzySecureSocketResponseApi(socketEncoder); + } +} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java new file mode 100644 index 00000000..76681ec9 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java @@ -0,0 +1,55 @@ +package com.tvd12.ezyfoxserver.api; + +import com.tvd12.ezyfoxserver.entity.EzySession; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.socket.EzyChannel; +import com.tvd12.ezyfoxserver.socket.EzyPacket; + +public class EzySecureSocketResponseApi extends EzySocketResponseApi { + + public EzySecureSocketResponseApi(Object encoder) { + super(encoder); + } + + @Override + protected void sendTcpPacket( + EzySession session, + EzyPacket packet + ) throws Exception { + EzyChannel channel = session.getChannel(); + if (channel == null) { + session.send(packet); + } + try { + synchronized (channel) { + byte[] packedBytes = channel.pack((byte[]) packet.getData()); + packet.replaceData(packedBytes); + session.send(packet); + } + } catch (EzyConnectionCloseException e) { + session.disconnect(); + throw e; + } + } + + @Override + protected void sendTcpPacketNow( + EzySession session, + EzyPacket packet + ) throws Exception { + EzyChannel channel = session.getChannel(); + if (channel == null) { + session.sendNow(packet); + } + try { + synchronized (channel) { + byte[] packedBytes = channel.pack((byte[]) packet.getData()); + packet.replaceData(packedBytes); + session.sendNow(packet); + } + } catch (EzyConnectionCloseException e) { + session.disconnect(); + throw e; + } + } +} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java index 47007e0f..75a55aac 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java @@ -6,9 +6,6 @@ import com.tvd12.ezyfox.constant.EzyConstant; import com.tvd12.ezyfox.entity.EzyArray; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; -import com.tvd12.ezyfoxserver.entity.EzySession; -import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; -import com.tvd12.ezyfoxserver.socket.EzyChannel; public class EzySocketResponseApi extends EzyAbstractResponseApi { @@ -45,23 +42,6 @@ protected byte[] encryptMessageContent( ); } - @Override - protected Object packMessage( - EzySession session, - Object message - ) throws Exception { - EzyChannel channel = session.getChannel(); - if (channel == null) { - return message; - } - try { - return channel.pack((byte[]) message); - } catch (EzyConnectionCloseException e) { - session.disconnect(); - throw e; - } - } - @Override protected EzyConstant getConnectionType() { return EzyConnectionType.SOCKET; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyPacket.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyPacket.java index b39119e5..1b2da7ab 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyPacket.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyPacket.java @@ -7,6 +7,8 @@ public interface EzyPacket extends EzyReleasable { Object getData(); + void replaceData(Object data); + boolean isBinary(); boolean isReleased(); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java index 0b3a7de1..c152fa2c 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySimplePacket.java @@ -17,6 +17,11 @@ public class EzySimplePacket implements EzyPacket { @Setter private EzyConstant transportType = EzyTransportType.TCP; + @Override + public void replaceData(Object data) { + this.data = data; + } + @Override public void setFragment(Object fragment) { this.data = fragment; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java index 0e0864ba..556eefc9 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java @@ -153,7 +153,7 @@ public SSLEngine handle( break; } } - if (handshakeStatus == NOT_HANDSHAKING) { + if (handshakeStatus != FINISHED) { throw new SSLException("not handshaking"); } return engine; diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index 420f0ab3..d4568a4f 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -2,10 +2,7 @@ import com.tvd12.ezyfox.concurrent.EzyExecutors; import com.tvd12.ezyfoxserver.EzyServerBootstrap; -import com.tvd12.ezyfoxserver.api.EzyProxyResponseApi; -import com.tvd12.ezyfoxserver.api.EzyProxyStreamingApi; -import com.tvd12.ezyfoxserver.api.EzyResponseApi; -import com.tvd12.ezyfoxserver.api.EzyStreamingApi; +import com.tvd12.ezyfoxserver.api.*; import com.tvd12.ezyfoxserver.builder.EzyHttpServerBootstrapBuilder; import com.tvd12.ezyfoxserver.codec.EzyCodecFactory; import com.tvd12.ezyfoxserver.codec.EzySimpleCodecFactory; @@ -104,7 +101,10 @@ protected EzyStreamingApi newStreamingApi() { } protected EzyResponseApi newResponseApi(EzyCodecFactory codecFactory) { - return new EzyProxyResponseApi(codecFactory); + EzySocketSetting socketSetting = getSocketSetting(); + return socketSetting.isCertificationSslActive() + ? new EzySecureProxyResponseApi(codecFactory) + : new EzyProxyResponseApi(codecFactory); } private ExecutorService newStatsThreadPool() { diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index eb5ebbc8..efa0f0ac 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -2,6 +2,8 @@ import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; @@ -9,6 +11,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import java.util.Arrays; import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; @@ -21,6 +24,8 @@ public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { @Getter private ByteBuffer readAppBuffer; + private final Logger logger = LoggerFactory.getLogger(getClass()); + public EzyNioSecureSocketChannel( SocketChannel channel ) { @@ -28,6 +33,7 @@ public EzyNioSecureSocketChannel( } public void setEngine(SSLEngine engine) { + logger.info("channel: {} set engine status: {}", channel, engine.getHandshakeStatus()); this.engine = engine; this.session = engine.getSession(); this.readAppBuffer = ByteBuffer.allocate(session.getApplicationBufferSize()); @@ -35,7 +41,9 @@ public void setEngine(SSLEngine engine) { @Override public byte[] pack(byte[] bytes) throws Exception { - ByteBuffer buffer = ByteBuffer.wrap(bytes); + ByteBuffer buffer = ByteBuffer.allocate(bytes.length); + buffer.put(bytes); + buffer.flip(); int netBufferLength = session.getPacketBufferSize(); ByteBuffer netBuffer = ByteBuffer.allocate(netBufferLength); while (buffer.hasRemaining()) { @@ -58,6 +66,16 @@ public byte[] pack(byte[] bytes) throws Exception { netBuffer.flip(); byte[] answer = new byte[netBuffer.limit()]; netBuffer.get(answer); + logger.info( + "wrap on channel: {}, from size: {}, to size: {} - {}, status: {} - {}, engine: {}", + channel, + bytes.length, + answer.length, + Arrays.toString(answer), + result.getHandshakeStatus(), + engine.getHandshakeStatus(), + engine + ); return answer; } } @@ -69,6 +87,5 @@ public void close() { super.close(); this.engine = null; this.readAppBuffer = null; - } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 86fb3b0b..f730d5c0 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -72,7 +72,27 @@ protected byte[] readTcpBytesFromBuffer( netBuffer.flip(); ByteBuffer tcpAppBuffer = ByteBuffer.allocate(appBufferSize); while (netBuffer.hasRemaining()) { - SSLEngineResult result = engine.unwrap(netBuffer, tcpAppBuffer); + SSLEngineResult result; + try { + /* + logger.info( + "before read on channel: {}, netbuffer: {}, appBuffer: {}", + channel, + netBuffer, + tcpAppBuffer + );*/ + result = engine.unwrap(netBuffer, tcpAppBuffer); + } catch (Exception e) { + /* + logger.info( + "after read on channel: {} error, netbuffer: {}, appBuffer: {}", + channel, + netBuffer, + tcpAppBuffer, + e + );*/ + throw e; + } switch (result.getStatus()) { case BUFFER_OVERFLOW: netBuffer = enlargeBuffer(netBuffer, appBufferSize); From a6c93c38a027d83890e28126fcf4cd01ab2a516a Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Mon, 7 Aug 2023 21:57:25 +0700 Subject: [PATCH 40/46] move logic to channel --- .../api/EzySecureSocketResponseApi.java | 15 +- .../tvd12/ezyfoxserver/socket/EzyChannel.java | 4 - .../ezyfoxserver/socket/EzySecureChannel.java | 8 + .../nio/EzyNioServerBootstrap.java | 1 + .../nio/EzySocketServerBootstrap.java | 15 +- .../socket/EzyNioSecureSocketAcceptor.java | 12 +- .../nio/socket/EzyNioSecureSocketChannel.java | 236 +++++++++++++++--- .../socket/EzySecureSocketDataReceiver.java | 70 +----- 8 files changed, 248 insertions(+), 113 deletions(-) create mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java index 76681ec9..9d0f93ed 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java @@ -4,6 +4,7 @@ import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.ezyfoxserver.socket.EzyPacket; +import com.tvd12.ezyfoxserver.socket.EzySecureChannel; public class EzySecureSocketResponseApi extends EzySocketResponseApi { @@ -20,9 +21,12 @@ protected void sendTcpPacket( if (channel == null) { session.send(packet); } + EzySecureChannel secureChannel = (EzySecureChannel) channel; try { - synchronized (channel) { - byte[] packedBytes = channel.pack((byte[]) packet.getData()); + synchronized (secureChannel.getPackLock()) { + byte[] packedBytes = secureChannel.pack( + (byte[]) packet.getData() + ); packet.replaceData(packedBytes); session.send(packet); } @@ -41,9 +45,12 @@ protected void sendTcpPacketNow( if (channel == null) { session.sendNow(packet); } + EzySecureChannel secureChannel = (EzySecureChannel) channel; try { - synchronized (channel) { - byte[] packedBytes = channel.pack((byte[]) packet.getData()); + synchronized (secureChannel.getPackLock()) { + byte[] packedBytes = secureChannel.pack( + (byte[]) packet.getData() + ); packet.replaceData(packedBytes); session.sendNow(packet); } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java index f20d2b40..48fb22d6 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzyChannel.java @@ -21,8 +21,4 @@ public interface EzyChannel { SocketAddress getServerAddress(); SocketAddress getClientAddress(); - - default byte[] pack(byte[] bytes) throws Exception { - return bytes; - } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java new file mode 100644 index 00000000..ec33f5dd --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java @@ -0,0 +1,8 @@ +package com.tvd12.ezyfoxserver.socket; + +public interface EzySecureChannel { + + byte[] pack(byte[] bytes) throws Exception; + + Object getPackLock(); +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java index e36d2e98..72f53c0c 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzyNioServerBootstrap.java @@ -130,6 +130,7 @@ private void startUserRemovalHandlingLoopHandlers() throws Exception { private EzySocketServerBootstrap newSocketServerBootstrap() { return EzySocketServerBootstrap.builder() .serverContext(context) + .sslContext(sslContext) .socketDataReceiver(socketDataReceiver) .handlerGroupManager(handlerGroupManager) .sessionTicketsQueue(socketSessionTicketsQueue) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 73cd368f..7d422602 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -8,6 +8,7 @@ import com.tvd12.ezyfoxserver.socket.EzySocketWriter; import com.tvd12.ezyfoxserver.socket.EzySocketWritingLoopHandler; +import javax.net.ssl.SSLContext; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.channels.SelectionKey; @@ -24,10 +25,12 @@ public class EzySocketServerBootstrap extends EzyAbstractSocketServerBootstrap { private ServerSocketChannel serverSocketChannel; private EzySocketEventLoopHandler readingLoopHandler; private EzySocketEventLoopHandler socketAcceptanceLoopHandler; + private final SSLContext sslContext; private final EzySocketSetting socketSetting; public EzySocketServerBootstrap(Builder builder) { super(builder); + this.sslContext = builder.sslContext; this.socketSetting = serverSettings.getSocket(); } @@ -86,7 +89,10 @@ private void startSocketHandlers() throws Exception { private EzyNioSocketAcceptor newSocketAcceptor() { return socketSetting.isCertificationSslActive() - ? new EzyNioSecureSocketAcceptor() + ? new EzyNioSecureSocketAcceptor( + sslContext, + socketSetting.getSslHandshakeTimeout() + ) : new EzyNioSocketAcceptor(); } @@ -148,6 +154,13 @@ public static class Builder extends EzyAbstractSocketServerBootstrap.Builder< EzySocketServerBootstrap > { + private SSLContext sslContext; + + public Builder sslContext(SSLContext sslContext) { + this.sslContext = sslContext; + return this; + } + @Override public EzySocketServerBootstrap build() { return new EzySocketServerBootstrap(this); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java index a5b0cff0..d90202cb 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java @@ -1,13 +1,23 @@ package com.tvd12.ezyfoxserver.nio.socket; import com.tvd12.ezyfoxserver.socket.EzyChannel; +import lombok.AllArgsConstructor; +import javax.net.ssl.SSLContext; import java.nio.channels.SocketChannel; +@AllArgsConstructor public class EzyNioSecureSocketAcceptor extends EzyNioSocketAcceptor { + private final SSLContext sslContext; + private final int sslHandshakeTimeout; + @Override protected EzyChannel newChannel(SocketChannel clientChannel) { - return new EzyNioSecureSocketChannel(clientChannel); + return new EzyNioSecureSocketChannel( + clientChannel, + sslContext, + sslHandshakeTimeout + ); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index efa0f0ac..9b00e2e7 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -1,51 +1,217 @@ package com.tvd12.ezyfoxserver.nio.socket; import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.socket.EzySecureChannel; import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLSession; +import javax.net.ssl.*; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.Arrays; import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; +import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; +import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; -public class EzyNioSecureSocketChannel extends EzyNioSocketChannel { +public class EzyNioSecureSocketChannel + extends EzyNioSocketChannel + implements EzySecureChannel { - @Getter private SSLEngine engine; - private SSLSession session; + private ByteBuffer netBuffer; @Getter - private ByteBuffer readAppBuffer; - + private boolean handshaked; + private int appBufferSize; + private int netBufferSize; + private final SSLContext sslContext; + private final int sslHandshakeTimeout; + @Getter + private final Object packLock = new Object(); private final Logger logger = LoggerFactory.getLogger(getClass()); public EzyNioSecureSocketChannel( - SocketChannel channel + SocketChannel channel, + SSLContext sslContext, + int sslHandshakeTimeout ) { super(channel); + this.sslContext = sslContext; + this.sslHandshakeTimeout = sslHandshakeTimeout; + } + + @SuppressWarnings("MethodLength") + public void handshake() throws IOException { + engine = sslContext.createSSLEngine(); + engine.setUseClientMode(false); + engine.beginHandshake(); + + SSLSession session = engine.getSession(); + appBufferSize = session.getApplicationBufferSize(); + netBufferSize = session.getPacketBufferSize(); + netBuffer = ByteBuffer.allocate(netBufferSize); + ByteBuffer appBuffer = ByteBuffer.allocate(appBufferSize); + ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); + ByteBuffer peerNetData = ByteBuffer.allocate(netBufferSize); + + SSLEngineResult result; + SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); + long currentTime = System.currentTimeMillis(); + long endTime = currentTime + sslHandshakeTimeout; + while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { + currentTime = System.currentTimeMillis(); + if (currentTime >= endTime) { + throw new SSLException("Timeout"); + } + switch (handshakeStatus) { + case NEED_UNWRAP: + int readBytes = channel.read(peerNetData); + if (readBytes < 0) { + if (engine.isInboundDone() && engine.isOutboundDone()) { + throw new SSLException( + "status is NEED_UNWRAP " + + "while inbound and outbound done" + ); + } + try { + engine.closeInbound(); + } catch (SSLException e) { + logger.info( + "This engine was forced to close inbound, " + + "without having received the proper SSL/TLS close " + + "notification message from the peer, due to end of stream.", + e + ); + } + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + peerNetData.flip(); + try { + result = engine.unwrap(peerNetData, peerAppData); + peerNetData.compact(); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException e) { + logger.info( + "A problem was encountered while processing the data " + + "that caused the SSLEngine to abort. " + + "Will try to properly close connection...", + e + ); + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + switch (result.getStatus()) { + case BUFFER_OVERFLOW: + peerAppData = enlargeBuffer(peerAppData, appBufferSize); + break; + case BUFFER_UNDERFLOW: + peerNetData = enlargeBufferIfNeed(peerNetData, netBufferSize); + break; + case CLOSED: + if (engine.isOutboundDone()) { + throw new SSLException("status CLOSED while outbound done"); + } else { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + break; + } + default: // OK + break; + } + break; + case NEED_WRAP: + netBuffer.clear(); + try { + result = engine.wrap(appBuffer, netBuffer); + handshakeStatus = result.getHandshakeStatus(); + } catch (SSLException e) { + engine.closeOutbound(); + handshakeStatus = engine.getHandshakeStatus(); + logger.info( + "A problem was encountered while processing the data " + + "that caused the SSLEngine to abort. " + + "Will try to properly close connection...", + e + ); + break; + } + switch (result.getStatus()) { + case BUFFER_OVERFLOW: + netBuffer = enlargeBuffer(netBuffer, netBufferSize); + break; + case BUFFER_UNDERFLOW: + throw new SSLException("Buffer underflow occurred after a wrap."); + case CLOSED: + try { + writeOrTimeout(channel, netBuffer, endTime); + peerNetData.clear(); + } catch (Exception e) { + logger.info( + "Failed to send server's close message " + + "due to socket channel's failure.", + e + ); + handshakeStatus = engine.getHandshakeStatus(); + } + break; + default: // OK + writeOrTimeout(channel, netBuffer, endTime); + break; + } + break; + default: // NEED_TASK: + Runnable task; + while ((task = engine.getDelegatedTask()) != null) { + task.run(); + } + handshakeStatus = engine.getHandshakeStatus(); + break; + } + } + handshaked = true; } - public void setEngine(SSLEngine engine) { - logger.info("channel: {} set engine status: {}", channel, engine.getHandshakeStatus()); - this.engine = engine; - this.session = engine.getSession(); - this.readAppBuffer = ByteBuffer.allocate(session.getApplicationBufferSize()); + public byte[] read(ByteBuffer buffer) throws Exception { + if (netBuffer.position() > 0) { + netBuffer.compact(); + } + netBuffer.put(buffer); + netBuffer.flip(); + ByteBuffer tcpAppBuffer = ByteBuffer.allocate(appBufferSize); + while (netBuffer.hasRemaining()) { + SSLEngineResult result = engine.unwrap(netBuffer, tcpAppBuffer); + switch (result.getStatus()) { + case BUFFER_OVERFLOW: + netBuffer = enlargeBuffer(netBuffer, appBufferSize); + break; + case BUFFER_UNDERFLOW: + tcpAppBuffer = enlargeBufferIfNeed(tcpAppBuffer, netBufferSize); + break; + case CLOSED: + safeCloseOutbound(engine); + throw new EzyConnectionCloseException( + "ssl unwrap result status is CLOSE" + ); + default: // 0K + tcpAppBuffer.flip(); + byte[] binary = new byte[tcpAppBuffer.limit()]; + tcpAppBuffer.get(binary); + return binary; + } + } + return new byte[0]; } @Override public byte[] pack(byte[] bytes) throws Exception { - ByteBuffer buffer = ByteBuffer.allocate(bytes.length); - buffer.put(bytes); - buffer.flip(); - int netBufferLength = session.getPacketBufferSize(); - ByteBuffer netBuffer = ByteBuffer.allocate(netBufferLength); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + ByteBuffer netBuffer = ByteBuffer.allocate(netBufferSize); while (buffer.hasRemaining()) { SSLEngineResult result = engine.wrap( buffer, @@ -53,7 +219,7 @@ public byte[] pack(byte[] bytes) throws Exception { ); switch (result.getStatus()) { case BUFFER_OVERFLOW: - netBuffer = enlargeBuffer(netBuffer, netBufferLength); + netBuffer = enlargeBuffer(netBuffer, netBufferSize); break; case BUFFER_UNDERFLOW: throw new IOException("Buffer underflow occurred after a wrap"); @@ -66,26 +232,24 @@ public byte[] pack(byte[] bytes) throws Exception { netBuffer.flip(); byte[] answer = new byte[netBuffer.limit()]; netBuffer.get(answer); - logger.info( - "wrap on channel: {}, from size: {}, to size: {} - {}, status: {} - {}, engine: {}", - channel, - bytes.length, - answer.length, - Arrays.toString(answer), - result.getHandshakeStatus(), - engine.getHandshakeStatus(), - engine - ); return answer; } } return bytes; } - @Override - public void close() { - super.close(); - this.engine = null; - this.readAppBuffer = null; + public static void writeOrTimeout( + SocketChannel channel, + ByteBuffer buffer, + long timeoutAt + ) throws IOException { + buffer.flip(); + while (buffer.hasRemaining()) { + long currentTime = System.currentTimeMillis(); + if (currentTime >= timeoutAt) { + throw new SSLException("Timeout"); + } + channel.write(buffer); + } } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index f730d5c0..67d6dc65 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -1,29 +1,19 @@ package com.tvd12.ezyfoxserver.nio.socket; import com.tvd12.ezyfoxserver.constant.EzyDisconnectReason; -import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLSession; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import static com.tvd12.ezyfoxserver.constant.EzyCoreConstants.MAX_SECURE_READ_BUFFER_SIZE; -import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { - protected final EzySslHandshakeHandler sslHandshakeHandler; - public EzySecureSocketDataReceiver(Builder builder) { super(builder); - this.sslHandshakeHandler = builder.sslHandshakeHandler; } @Override @@ -36,11 +26,9 @@ protected void tcpReadBytes( if (handlerGroup != null) { EzyNioSecureSocketChannel secureChannel = (EzyNioSecureSocketChannel) handlerGroup.getChannel(); - if (secureChannel.getEngine() == null) { + if (!secureChannel.isHandshaked()) { try { - secureChannel.setEngine( - sslHandshakeHandler.handle(channel) - ); + secureChannel.handshake(); } catch (Exception e) { logger.info("handshake failed on channel: {}", channel, e); handlerGroup.enqueueDisconnection( @@ -60,59 +48,7 @@ protected byte[] readTcpBytesFromBuffer( ) throws Exception { EzyNioSecureSocketChannel secureChannel = (EzyNioSecureSocketChannel) channel; - SSLEngine engine = secureChannel.getEngine(); - SSLSession session = engine.getSession(); - int appBufferSize = session.getApplicationBufferSize(); - int packageBufferSize = session.getPacketBufferSize(); - ByteBuffer netBuffer = secureChannel.getReadAppBuffer(); - if (netBuffer.position() > 0) { - netBuffer.compact(); - } - netBuffer.put(buffer); - netBuffer.flip(); - ByteBuffer tcpAppBuffer = ByteBuffer.allocate(appBufferSize); - while (netBuffer.hasRemaining()) { - SSLEngineResult result; - try { - /* - logger.info( - "before read on channel: {}, netbuffer: {}, appBuffer: {}", - channel, - netBuffer, - tcpAppBuffer - );*/ - result = engine.unwrap(netBuffer, tcpAppBuffer); - } catch (Exception e) { - /* - logger.info( - "after read on channel: {} error, netbuffer: {}, appBuffer: {}", - channel, - netBuffer, - tcpAppBuffer, - e - );*/ - throw e; - } - switch (result.getStatus()) { - case BUFFER_OVERFLOW: - netBuffer = enlargeBuffer(netBuffer, appBufferSize); - break; - case BUFFER_UNDERFLOW: - tcpAppBuffer = enlargeBufferIfNeed(tcpAppBuffer, packageBufferSize); - break; - case CLOSED: - safeCloseOutbound(engine); - throw new EzyConnectionCloseException( - "ssl unwrap result status is CLOSE" - ); - default: // 0K - tcpAppBuffer.flip(); - byte[] binary = new byte[tcpAppBuffer.limit()]; - tcpAppBuffer.get(binary); - return binary; - } - } - return new byte[0]; + return secureChannel.read(buffer); } @Override From 68e8ba3ceb82e78293070cdf711c015151dd5295 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Tue, 8 Aug 2023 23:12:44 +0700 Subject: [PATCH 41/46] try to complete unit test --- .../api/EzyAbstractResponseApi.java | 24 +- .../api/EzySecureSocketResponseApi.java | 6 +- .../constant/EzyCoreConstants.java | 1 - .../setting/EzySimpleSocketSetting.java | 10 - .../setting/EzySocketSetting.java | 4 - .../setting/EzySocketSettingBuilder.java | 20 - .../socket/EzySocketChannels.java | 26 - .../tvd12/ezyfoxserver/ssl/EzySslEngines.java | 23 - .../ssl/EzySslHandshakeHandler.java | 161 --- .../ezyfoxserver/ssl/SslByteBuffers.java | 34 - .../src/main/resources/ezy-settings-1.0.0.xsd | 2 - .../src/main/resources/ezy-settings.xml | 2 - .../api/EzyAbstractResponseApiTest.java | 234 +++- .../api/EzySecureSocketResponseApiTest.java | 191 ++++ .../testing/api/EzySocketResponseApiTest.java | 73 -- .../setting/EzySocketSettingBuilderTest.java | 12 - .../testing/socket/EzyChannelTest.java | 66 -- .../testing/socket/EzySimplePacketTest.java | 15 + .../testing/socket/EzySocketChannelsTest.java | 38 - .../testing/ssl/EzySslEnginesTest.java | 37 - .../ssl/EzySslHandshakeHandlerTest.java | 824 -------------- .../testing/ssl/SslByteBuffersTest.java | 46 - .../EzyNioServerBootstrapBuilderImpl.java | 26 +- .../nio/socket/EzyNioSecureSocketChannel.java | 44 +- .../socket/EzySecureSocketDataReceiver.java | 15 - .../testing/EzySocketServerBootstrapTest.java | 13 +- .../handler/EzyAbstractHandlerGroupTest.java | 3 +- .../socket/EzyNioSecureSocketChannelTest.java | 1013 ++++++++++++++++- .../socket/EzyNioSocketAcceptorTest.java | 47 +- .../socket/EzyNioSocketReaderTest.java | 4 +- .../socket}/EzySSLContextSpiForTest.java | 2 +- .../EzySecureSocketDataReceiverTest.java | 261 +---- 32 files changed, 1585 insertions(+), 1692 deletions(-) delete mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java delete mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java delete mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java delete mode 100644 ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java delete mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java delete mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java delete mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java delete mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java delete mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java rename {ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl => ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket}/EzySSLContextSpiForTest.java (80%) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java index c1578a60..718f612f 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java @@ -40,27 +40,35 @@ protected final void normalResponse( EzyConstant transportType = pack.getTransportType(); if (immediate) { for (EzySession session : recipients) { + EzyPacket packet = null; try { - EzyPacket packet = createPacket( + packet = createPacket( session, transportType, bytes ); sendPacketNow(session, packet); } catch (Throwable e) { + if (packet != null) { + packet.release(); + } logger.info("response data now to session: {} failed", session, e); } } } else { for (EzySession session : recipients) { + EzyPacket packet = null; try { - EzyPacket packet = createPacket( + packet = createPacket( session, transportType, bytes ); sendPacket(session, packet); } catch (Throwable e) { + if (packet != null) { + packet.release(); + } logger.info("response data to session: {} failed", session, e); } } @@ -80,29 +88,37 @@ protected final void secureResponse( EzyConstant transportType = pack.getTransportType(); if (immediate) { for (EzySession session : recipients) { + EzyPacket packet = null; try { byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - EzyPacket packet = createPacket( + packet = createPacket( session, transportType, bytes ); sendPacketNow(session, packet); } catch (Throwable e) { + if (packet != null) { + packet.release(); + } logger.info("response data now to session: {} failed", session, e); } } } else { for (EzySession session : recipients) { + EzyPacket packet = null; try { byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - EzyPacket packet = createPacket( + packet = createPacket( session, transportType, bytes ); sendPacket(session, packet); } catch (Throwable e) { + if (packet != null) { + packet.release(); + } logger.info("response data to session: {} failed", session, e); } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java index 9d0f93ed..0b59a8fb 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java @@ -6,6 +6,8 @@ import com.tvd12.ezyfoxserver.socket.EzyPacket; import com.tvd12.ezyfoxserver.socket.EzySecureChannel; +import java.io.IOException; + public class EzySecureSocketResponseApi extends EzySocketResponseApi { public EzySecureSocketResponseApi(Object encoder) { @@ -19,7 +21,7 @@ protected void sendTcpPacket( ) throws Exception { EzyChannel channel = session.getChannel(); if (channel == null) { - session.send(packet); + throw new IOException("session disconnected"); } EzySecureChannel secureChannel = (EzySecureChannel) channel; try { @@ -43,7 +45,7 @@ protected void sendTcpPacketNow( ) throws Exception { EzyChannel channel = session.getChannel(); if (channel == null) { - session.sendNow(packet); + throw new IOException("session disconnected"); } EzySecureChannel secureChannel = (EzySecureChannel) channel; try { diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java index d8de98d1..8521e860 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/EzyCoreConstants.java @@ -3,7 +3,6 @@ public final class EzyCoreConstants { public static final int MAX_READ_BUFFER_SIZE = 4096; - public static final int MAX_SECURE_READ_BUFFER_SIZE = 16709; private EzyCoreConstants() {} } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java index 854155f8..c4926b4e 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java @@ -32,12 +32,6 @@ public class EzySimpleSocketSetting @XmlElement(name = "tcp-no-delay") protected boolean tcpNoDelay; - @XmlElement(name = "connection-acceptor-thread-pool-size") - protected int connectionAcceptorThreadPoolSize; - - @XmlElement(name = "ssl-connection-acceptor-thread-pool-size") - protected int sslConnectionAcceptorThreadPoolSize; - @XmlElement(name = "writer-thread-pool-size") protected int writerThreadPoolSize; @@ -47,8 +41,6 @@ public EzySimpleSocketSetting() { setSslType(SslType.CUSTOMIZATION); setSslHandshakeTimeout(350); setMaxRequestSize(4096); - setConnectionAcceptorThreadPoolSize(1); - setSslConnectionAcceptorThreadPoolSize(8); setWriterThreadPoolSize(8); setCodecCreator("com.tvd12.ezyfox.codec.MsgPackCodecCreator"); } @@ -62,8 +54,6 @@ public Map toMap() { map.put("tcpNoDelay", tcpNoDelay); map.put("maxRequestSize", maxRequestSize); map.put("writerThreadPoolSize", writerThreadPoolSize); - map.put("connectionAcceptorThreadPoolSize", connectionAcceptorThreadPoolSize); - map.put("sslConnectionAcceptorThreadPoolSize", sslConnectionAcceptorThreadPoolSize); return map; } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 9687b880..cf0de8e3 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java @@ -11,10 +11,6 @@ public interface EzySocketSetting extends EzyBaseSocketSetting { int getMaxRequestSize(); - int getConnectionAcceptorThreadPoolSize(); - - int getSslConnectionAcceptorThreadPoolSize(); - int getWriterThreadPoolSize(); default boolean isCertificationSslActive() { diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java index cba3c9f5..bbe30746 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSettingBuilder.java @@ -11,8 +11,6 @@ public class EzySocketSettingBuilder extends protected int sslHandshakeTimeout; protected int maxRequestSize; protected boolean tcpNoDelay; - protected int connectionAcceptorThreadPoolSize; - protected int sslConnectionAcceptorThreadPoolSize; protected int writerThreadPoolSize; public EzySocketSettingBuilder() { @@ -20,8 +18,6 @@ public EzySocketSettingBuilder() { this.sslType = SslType.CUSTOMIZATION; this.sslHandshakeTimeout = 350; this.maxRequestSize = 32768; - this.connectionAcceptorThreadPoolSize = 1; - this.sslConnectionAcceptorThreadPoolSize = 8; this.writerThreadPoolSize = 8; this.codecCreator = "com.tvd12.ezyfox.codec.MsgPackCodecCreator"; } @@ -46,20 +42,6 @@ public EzySocketSettingBuilder tcpNoDelay(boolean tcpNoDelay) { return this; } - public EzySocketSettingBuilder connectionAcceptorThreadPoolSize( - int connectionAcceptorThreadPoolSize - ) { - this.connectionAcceptorThreadPoolSize = connectionAcceptorThreadPoolSize; - return this; - } - - public EzySocketSettingBuilder sslConnectionAcceptorThreadPoolSize( - int sslConnectionAcceptorThreadPoolSize - ) { - this.sslConnectionAcceptorThreadPoolSize = sslConnectionAcceptorThreadPoolSize; - return this; - } - public EzySocketSettingBuilder writerThreadPoolSize(int writerThreadPoolSize) { this.writerThreadPoolSize = writerThreadPoolSize; return this; @@ -73,8 +55,6 @@ protected EzySimpleSocketSetting newSetting() { setting.setSslType(sslType); setting.setSslHandshakeTimeout(sslHandshakeTimeout); setting.setMaxRequestSize(maxRequestSize); - setting.setConnectionAcceptorThreadPoolSize(connectionAcceptorThreadPoolSize); - setting.setSslConnectionAcceptorThreadPoolSize(sslConnectionAcceptorThreadPoolSize); setting.setWriterThreadPoolSize(writerThreadPoolSize); setting.setCodecCreator(codecCreator); return setting; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java deleted file mode 100644 index 5d6afff8..00000000 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySocketChannels.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.tvd12.ezyfoxserver.socket; - -import javax.net.ssl.SSLException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; - -public final class EzySocketChannels { - - private EzySocketChannels() {} - - public static void write( - SocketChannel socketChannel, - ByteBuffer buffer, - long timeoutAt - ) throws IOException { - buffer.flip(); - while (buffer.hasRemaining()) { - long currentTime = System.currentTimeMillis(); - if (currentTime >= timeoutAt) { - throw new SSLException("Timeout"); - } - socketChannel.write(buffer); - } - } -} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java deleted file mode 100644 index c1bcd24c..00000000 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslEngines.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.tvd12.ezyfoxserver.ssl; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.SSLEngine; - -public final class EzySslEngines { - - private static final Logger LOGGER = LoggerFactory.getLogger( - EzySslEngines.class - ); - - private EzySslEngines() {} - - public static void safeCloseOutbound(SSLEngine sslEngine) { - try { - sslEngine.closeOutbound(); - } catch (Throwable e) { - LOGGER.info("close outbound error", e); - } - } -} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java deleted file mode 100644 index 556eefc9..00000000 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/EzySslHandshakeHandler.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.tvd12.ezyfoxserver.ssl; - -import com.tvd12.ezyfox.util.EzyLoggable; -import lombok.AllArgsConstructor; - -import javax.net.ssl.*; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; - -import static com.tvd12.ezyfoxserver.socket.EzySocketChannels.write; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; -import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; -import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - -@AllArgsConstructor -public class EzySslHandshakeHandler extends EzyLoggable { - - private final SSLContext sslContext; - private final int timeout; - - @SuppressWarnings("MethodLength") - public SSLEngine handle( - SocketChannel socketChannel - ) throws IOException { - SSLEngine engine = sslContext.createSSLEngine(); - engine.setUseClientMode(false); - engine.beginHandshake(); - - SSLSession session = engine.getSession(); - int appBufferSize = session.getApplicationBufferSize(); - int packetBufferSize = session.getPacketBufferSize(); - ByteBuffer appBuffer = ByteBuffer.allocate(appBufferSize); - ByteBuffer netBuffer = ByteBuffer.allocate(packetBufferSize); - ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); - ByteBuffer peerNetData = ByteBuffer.allocate(packetBufferSize); - - SSLEngineResult result; - SSLEngineResult.HandshakeStatus handshakeStatus = engine.getHandshakeStatus(); - long currentTime = System.currentTimeMillis(); - long endTime = currentTime + timeout; - while (handshakeStatus != FINISHED && handshakeStatus != NOT_HANDSHAKING) { - currentTime = System.currentTimeMillis(); - if (currentTime >= endTime) { - throw new SSLException("Timeout"); - } - switch (handshakeStatus) { - case NEED_UNWRAP: - int readBytes = socketChannel.read(peerNetData); - if (readBytes < 0) { - if (engine.isInboundDone() && engine.isOutboundDone()) { - throw new SSLException( - "status is NEED_UNWRAP " + - "while inbound and outbound done" - ); - } - try { - engine.closeInbound(); - } catch (SSLException e) { - logger.info( - "This engine was forced to close inbound, " + - "without having received the proper SSL/TLS close " + - "notification message from the peer, due to end of stream.", - e - ); - } - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - break; - } - peerNetData.flip(); - try { - result = engine.unwrap(peerNetData, peerAppData); - peerNetData.compact(); - handshakeStatus = result.getHandshakeStatus(); - } catch (SSLException e) { - logger.info( - "A problem was encountered while processing the data " + - "that caused the SSLEngine to abort. " + - "Will try to properly close connection...", - e - ); - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - break; - } - switch (result.getStatus()) { - case BUFFER_OVERFLOW: - peerAppData = enlargeBuffer(peerAppData, appBufferSize); - break; - case BUFFER_UNDERFLOW: - peerNetData = enlargeBufferIfNeed(peerNetData, packetBufferSize); - break; - case CLOSED: - if (engine.isOutboundDone()) { - throw new SSLException("status CLOSED while outbound done"); - } else { - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - break; - } - default: // OK - break; - } - break; - case NEED_WRAP: - netBuffer.clear(); - try { - result = engine.wrap(appBuffer, netBuffer); - handshakeStatus = result.getHandshakeStatus(); - } catch (SSLException e) { - engine.closeOutbound(); - handshakeStatus = engine.getHandshakeStatus(); - logger.info( - "A problem was encountered while processing the data " + - "that caused the SSLEngine to abort. " + - "Will try to properly close connection...", - e - ); - break; - } - switch (result.getStatus()) { - case BUFFER_OVERFLOW: - netBuffer = enlargeBuffer(netBuffer, packetBufferSize); - break; - case BUFFER_UNDERFLOW: - throw new SSLException("Buffer underflow occurred after a wrap."); - case CLOSED: - try { - write(socketChannel, netBuffer, endTime); - peerNetData.clear(); - } catch (Exception e) { - logger.info( - "Failed to send server's close message " + - "due to socket channel's failure.", - e - ); - handshakeStatus = engine.getHandshakeStatus(); - } - break; - default: // OK - write(socketChannel, netBuffer, endTime); - break; - } - break; - default: // NEED_TASK: - Runnable task; - while ((task = engine.getDelegatedTask()) != null) { - task.run(); - } - handshakeStatus = engine.getHandshakeStatus(); - break; - } - } - if (handshakeStatus != FINISHED) { - throw new SSLException("not handshaking"); - } - return engine; - } -} diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java deleted file mode 100644 index fd9a5ec6..00000000 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/ssl/SslByteBuffers.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.tvd12.ezyfoxserver.ssl; - -import java.nio.ByteBuffer; - -public final class SslByteBuffers { - - private SslByteBuffers() {} - - public static ByteBuffer enlargeBuffer( - ByteBuffer buffer, - int sessionProposedCapacity - ) { - return sessionProposedCapacity > buffer.capacity() - ? ByteBuffer.allocate(sessionProposedCapacity) - : ByteBuffer.allocate(buffer.capacity() * 2); - } - - public static ByteBuffer enlargeBufferIfNeed( - ByteBuffer buffer, - int packageBufferSize - ) { - if (packageBufferSize < buffer.limit()) { - return buffer; - } else { - ByteBuffer replaceBuffer = enlargeBuffer( - buffer, - packageBufferSize - ); - buffer.flip(); - replaceBuffer.put(buffer); - return replaceBuffer; - } - } -} diff --git a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd index 0732b7bb..886c00a2 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd +++ b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd @@ -68,8 +68,6 @@ - - diff --git a/ezyfox-server-core/src/main/resources/ezy-settings.xml b/ezyfox-server-core/src/main/resources/ezy-settings.xml index d6f668b0..7104c6f5 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-settings.xml @@ -31,8 +31,6 @@ 300 true 4096 - 1 - 8 8 com.tvd12.ezyfoxserver.netty.codec.MsgPackCodecCreator diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java index d8822f23..23074535 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java @@ -5,9 +5,12 @@ import com.tvd12.ezyfox.factory.EzyEntityFactory; import com.tvd12.ezyfoxserver.api.EzyAbstractResponseApi; import com.tvd12.ezyfoxserver.constant.EzyConnectionType; +import com.tvd12.ezyfoxserver.constant.EzyTransportType; import com.tvd12.ezyfoxserver.entity.EzySession; import com.tvd12.ezyfoxserver.response.EzyPackage; +import com.tvd12.ezyfoxserver.socket.EzyDatagramChannelPool; import com.tvd12.ezyfoxserver.socket.EzyPacket; +import com.tvd12.ezyfoxserver.socket.EzySimplePacket; import com.tvd12.test.assertion.Asserts; import com.tvd12.test.reflect.MethodInvoker; import com.tvd12.test.reflect.MethodUtil; @@ -75,6 +78,125 @@ public void encryptMessageContentTest() { Asserts.assertEquals(UnsupportedOperationException.class, e.getCause().getCause().getClass()); } + @Test + public void normalResponseActualTcp() throws Exception { + // given + InternalResponseApi sut = new InternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + when(pack.getTransportType()).thenReturn(EzyTransportType.UDP_OR_TCP); + + EzySession session = mock(EzySession.class); + + doAnswer((it) -> { + EzyPacket packet = it.getArgumentAt(0, EzyPacket.class); + Asserts.assertEquals(packet.getTransportType(), EzyTransportType.TCP); + return null; + }).when(session).send(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, false); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).send(any(EzyPacket.class)); + verify(session, times(1)).getDatagramChannelPool(); + verifyNoMoreInteractions(session); + } + + @Test + public void normalResponseActualUdp() throws Exception { + // given + InternalResponseApi sut = new InternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + when(pack.getTransportType()).thenReturn(EzyTransportType.UDP_OR_TCP); + + EzySession session = mock(EzySession.class); + EzyDatagramChannelPool datagramChannelPool = mock(EzyDatagramChannelPool.class); + when(session.getDatagramChannelPool()).thenReturn(datagramChannelPool); + + doAnswer((it) -> { + EzyPacket packet = it.getArgumentAt(0, EzyPacket.class); + Asserts.assertEquals(packet.getTransportType(), EzyTransportType.UDP); + return null; + }).when(session).send(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, false); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).send(any(EzyPacket.class)); + verify(session, times(1)).getDatagramChannelPool(); + verifyNoMoreInteractions(session); + + verifyNoMoreInteractions(datagramChannelPool); + } + + @Test + public void immediateResponseActualUdp() throws Exception { + // given + InternalResponseApi sut = new InternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + when(pack.getTransportType()).thenReturn(EzyTransportType.UDP_OR_TCP); + + EzySession session = mock(EzySession.class); + EzyDatagramChannelPool datagramChannelPool = mock(EzyDatagramChannelPool.class); + when(session.getDatagramChannelPool()).thenReturn(datagramChannelPool); + + doAnswer((it) -> { + EzyPacket packet = it.getArgumentAt(0, EzyPacket.class); + Asserts.assertEquals(packet.getTransportType(), EzyTransportType.UDP); + return null; + }).when(session).sendNow(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, true); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).sendNow(any(EzyPacket.class)); + verify(session, times(1)).getDatagramChannelPool(); + verifyNoMoreInteractions(session); + + verifyNoMoreInteractions(datagramChannelPool); + } + @Test public void normalResponseImmediateSendException() throws Exception { // given @@ -106,6 +228,34 @@ public void normalResponseImmediateSendException() throws Exception { verifyNoMoreInteractions(session); } + @Test + public void normalResponseImmediateSendExceptionDueToCreatePacket() throws Exception { + // given + InternalResponseApi sut = new CreatePackFailedInternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + + EzySession session = mock(EzySession.class); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, true); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verifyNoMoreInteractions(session); + } + @Test public void normalResponseSendException() throws Exception { // given @@ -137,6 +287,34 @@ public void normalResponseSendException() throws Exception { verifyNoMoreInteractions(session); } + @Test + public void normalResponseSendExceptionDueToCreatePacket() throws Exception { + // given + InternalResponseApi sut = new CreatePackFailedInternalResponseApi(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(false); + + EzySession session = mock(EzySession.class); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, false); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verifyNoMoreInteractions(session); + } + @Test public void secureResponseImmediateSendException() throws Exception { // given @@ -170,16 +348,43 @@ public void secureResponseImmediateSendException() throws Exception { } @Test - public void secureResponseSendException() throws Exception { + public void secureResponseImmediateSendDueToCreatePacketException() throws Exception { // given - InternalResponseApi2 sut = new InternalResponseApi2(); + InternalResponseApi2 sut = new CreatePackFailedInternalResponseApi2(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(true); + + EzySession session = mock(EzySession.class); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, true); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).getSessionKey(); + verifyNoMoreInteractions(session); + } + + @Test + public void secureResponseSendDueCreatePacketException() throws Exception { + // given + InternalResponseApi2 sut = new CreatePackFailedInternalResponseApi2(); EzyPackage pack = mock(EzyPackage.class); when(pack.isEncrypted()).thenReturn(true); EzySession session = mock(EzySession.class); - RuntimeException error = new RuntimeException("test"); - doThrow(error).when(session).send(any(EzyPacket.class)); when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( Collections.singleton(session) @@ -196,7 +401,6 @@ public void secureResponseSendException() throws Exception { verifyNoMoreInteractions(pack); - verify(session, times(1)).send(any(EzyPacket.class)); verify(session, times(1)).getSessionKey(); verifyNoMoreInteractions(session); } @@ -227,13 +431,29 @@ protected Object encodeData(EzyArray data) { } @Override - protected byte[] dataToMessageContent(EzyArray data) throws Exception { + protected byte[] dataToMessageContent(EzyArray data) { return new byte[0]; } @Override - protected byte[] encryptMessageContent(byte[] messageContent, byte[] encryptionKey) throws Exception { + protected byte[] encryptMessageContent(byte[] messageContent, byte[] encryptionKey) { return messageContent; } } + + private static class CreatePackFailedInternalResponseApi extends InternalResponseApi { + + @Override + protected EzySimplePacket createPacket(EzyConstant transportType, Object bytes) { + throw new RuntimeException("test"); + } + } + + private static class CreatePackFailedInternalResponseApi2 extends InternalResponseApi2 { + + @Override + protected EzySimplePacket createPacket(EzyConstant transportType, Object bytes) { + throw new RuntimeException("test"); + } + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java new file mode 100644 index 00000000..02b5a453 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java @@ -0,0 +1,191 @@ +package com.tvd12.ezyfoxserver.testing.api; + +import com.tvd12.ezyfox.codec.EzyObjectToByteEncoder; +import com.tvd12.ezyfoxserver.api.EzySecureSocketResponseApi; +import com.tvd12.ezyfoxserver.entity.EzySession; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.socket.EzyChannel; +import com.tvd12.ezyfoxserver.socket.EzyPacket; +import com.tvd12.ezyfoxserver.socket.EzySecureChannel; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.MethodInvoker; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.IOException; + +import static org.mockito.Mockito.*; + +public class EzySecureSocketResponseApiTest { + + private EzySession session; + private CombineChannel channel; + private EzyPacket packet; + private EzyObjectToByteEncoder encoder; + private final byte[] data = new byte[0]; + private final byte[] packedData = new byte[0]; + private final Object packLock = new Object(); + private EzySecureSocketResponseApi instance; + + @BeforeMethod + public void setup() { + session = mock(EzySession.class); + channel = mock(CombineChannel.class); + packet = mock(EzyPacket.class); + encoder = mock(EzyObjectToByteEncoder.class); + instance = new EzySecureSocketResponseApi(encoder); + + when(session.getChannel()).thenReturn(channel); + when(channel.getPackLock()).thenReturn(packLock); + when(packet.getData()).thenReturn(data); + } + + @AfterMethod + private void verifyAll() { + verifyNoMoreInteractions(encoder); + + verify(session, times(1)).getChannel(); + verifyNoMoreInteractions(session); + + verifyNoMoreInteractions(channel); + verifyNoMoreInteractions(packet); + } + + @Test + public void sendTcpPacketNormalCase() throws Exception { + // given + when(channel.pack(data)).thenReturn(packedData); + + MethodInvoker.create() + .object(instance) + .method("sendTcpPacket") + .param(EzySession.class, session) + .param(EzyPacket.class, packet) + .invoke(); + + // then + verify(channel, times(1)).pack(data); + verify(channel, times(1)).getPackLock(); + + verify(packet, times(1)).getData(); + verify(packet, times(1)).replaceData(packedData); + + verify(session, times(1)).send(packet); + } + + @Test + public void sendTcpPacketThrowCloseExceptionCase() throws Exception { + // given + EzyConnectionCloseException exception = + new EzyConnectionCloseException("test"); + when(channel.pack(data)).thenThrow(exception); + + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(instance) + .method("sendTcpPacket") + .param(EzySession.class, session) + .param(EzyPacket.class, packet) + .invoke() + ); + + // then + Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); + + verify(channel, times(1)).pack(data); + verify(channel, times(1)).getPackLock(); + + verify(packet, times(1)).getData(); + + verify(session, times(1)).disconnect(); + } + + @Test + public void sendTcpPacketSessionDisconnectedExceptionCase() { + // given + when(session.getChannel()).thenReturn(null); + + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(instance) + .method("sendTcpPacket") + .param(EzySession.class, session) + .param(EzyPacket.class, packet) + .invoke() + ); + + // then + Asserts.assertEqualsType(e.getCause().getCause(), IOException.class); + } + + @Test + public void sendTcpPacketNowNormalCase() throws Exception { + // given + when(channel.pack(data)).thenReturn(packedData); + + MethodInvoker.create() + .object(instance) + .method("sendTcpPacketNow") + .param(EzySession.class, session) + .param(EzyPacket.class, packet) + .invoke(); + + // then + verify(channel, times(1)).pack(data); + verify(channel, times(1)).getPackLock(); + + verify(packet, times(1)).getData(); + verify(packet, times(1)).replaceData(packedData); + + verify(session, times(1)).sendNow(packet); + } + + @Test + public void sendTcpPacketNowThrowCloseExceptionCase() throws Exception { + // given + EzyConnectionCloseException exception = + new EzyConnectionCloseException("test"); + when(channel.pack(data)).thenThrow(exception); + + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(instance) + .method("sendTcpPacketNow") + .param(EzySession.class, session) + .param(EzyPacket.class, packet) + .invoke() + ); + + // then + Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); + + verify(channel, times(1)).pack(data); + verify(channel, times(1)).getPackLock(); + + verify(packet, times(1)).getData(); + + verify(session, times(1)).disconnect(); + } + + @Test + public void sendTcpPacketNowSessionDisconnectedExceptionCase() { + // given + when(session.getChannel()).thenReturn(null); + + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(instance) + .method("sendTcpPacketNow") + .param(EzySession.class, session) + .param(EzyPacket.class, packet) + .invoke() + ); + + // then + Asserts.assertEqualsType(e.getCause().getCause(), IOException.class); + } + + public interface CombineChannel + extends EzyChannel, EzySecureChannel {} +} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java index 88d567e2..fe1e53eb 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySocketResponseApiTest.java @@ -7,11 +7,7 @@ import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.constant.EzyTransportType; import com.tvd12.ezyfoxserver.entity.EzySession; -import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; -import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.ezyfoxserver.socket.EzySimplePackage; -import com.tvd12.test.assertion.Asserts; -import com.tvd12.test.reflect.MethodInvoker; import com.tvd12.test.util.RandomUtil; import org.testng.annotations.Test; @@ -116,73 +112,4 @@ public void secureResponseImmediateTest() throws Exception { verify(encoder, times(1)).toMessageContent(data); verify(encoder, times(sessionCount)).encryptMessageContent(any(byte[].class), any(byte[].class)); } - - @Test - public void packMessage() throws Exception { - // given - EzyObjectToByteEncoder encoder = mock(EzyObjectToByteEncoder.class); - EzySocketResponseApi instance = new EzySocketResponseApi(encoder); - - byte[] message = RandomUtil.randomShortByteArray(); - EzyChannel channel = mock(EzyChannel.class); - when(channel.pack(message)).thenReturn(message); - - EzySession session = mock(EzySession.class); - when(session.getChannel()).thenReturn(channel); - - // when - Object actual = MethodInvoker.create() - .object(instance) - .method("packMessage") - .param(EzySession.class, session) - .param(Object.class, message) - .invoke(); - - // then - Asserts.assertEquals(actual, message); - - verify(session, times(1)).getChannel(); - verifyNoMoreInteractions(session); - - verify(channel, times(1)).pack(message); - verifyNoMoreInteractions(channel); - } - - @Test - public void packMessageThrowsException() throws Exception { - // given - EzyObjectToByteEncoder encoder = mock(EzyObjectToByteEncoder.class); - EzySocketResponseApi instance = new EzySocketResponseApi(encoder); - - byte[] message = RandomUtil.randomShortByteArray(); - EzyChannel channel = mock(EzyChannel.class); - EzyConnectionCloseException error = new EzyConnectionCloseException("test"); - when(channel.pack(message)).thenThrow(error); - - EzySession session = mock(EzySession.class); - when(session.getChannel()).thenReturn(channel); - - // when - Throwable e = Asserts.assertThrows(() -> - MethodInvoker.create() - .object(instance) - .method("packMessage") - .param(EzySession.class, session) - .param(Object.class, message) - .invoke() - ); - - // then - Asserts.assertEqualsType( - e.getCause().getCause(), - EzyConnectionCloseException.class - ); - - verify(session, times(1)).getChannel(); - verify(session, times(1)).disconnect(); - verifyNoMoreInteractions(session); - - verify(channel, times(1)).pack(message); - verifyNoMoreInteractions(channel); - } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java index 90de4083..3c8bccbc 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java @@ -16,15 +16,11 @@ public void test() { SslType.class ); int sslHandshakeTimeout = RandomUtil.randomInt(); - int connectionAcceptorThreadPoolSize = RandomUtil.randomInt(); - int sslConnectionAcceptorThreadPoolSize = RandomUtil.randomInt(); // when EzySocketSetting setting = new EzySocketSettingBuilder() .sslType(sslType) .sslHandshakeTimeout(sslHandshakeTimeout) - .connectionAcceptorThreadPoolSize(connectionAcceptorThreadPoolSize) - .sslConnectionAcceptorThreadPoolSize(sslConnectionAcceptorThreadPoolSize) .build(); // then @@ -33,13 +29,5 @@ public void test() { setting.getSslHandshakeTimeout(), sslHandshakeTimeout ); - Asserts.assertEquals( - setting.getConnectionAcceptorThreadPoolSize(), - connectionAcceptorThreadPoolSize - ); - Asserts.assertEquals( - setting.getSslConnectionAcceptorThreadPoolSize(), - sslConnectionAcceptorThreadPoolSize - ); } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java deleted file mode 100644 index 75ec74a6..00000000 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzyChannelTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.tvd12.ezyfoxserver.testing.socket; - -import com.tvd12.ezyfoxserver.constant.EzyConnectionType; -import com.tvd12.ezyfoxserver.socket.EzyChannel; -import com.tvd12.test.assertion.Asserts; -import com.tvd12.test.util.RandomUtil; -import org.testng.annotations.Test; - -import java.net.SocketAddress; - -public class EzyChannelTest { - - @Test - public void pack() throws Exception { - // given - EzyChannel channel = new TestEzyChannel(); - byte[] bytes = RandomUtil.randomShortByteArray(); - - // when - byte[] actual = channel.pack(bytes); - - // then - Asserts.assertEquals(actual, bytes); - } - - public static class TestEzyChannel implements EzyChannel { - - @Override - public void close() { - } - - @Override - public void disconnect() { - } - - @Override - public boolean isConnected() { - return false; - } - - @Override - public int write(Object data, boolean binary) throws Exception { - return 0; - } - - @Override - public T getConnection() { - return null; - } - - @Override - public EzyConnectionType getConnectionType() { - return null; - } - - @Override - public SocketAddress getServerAddress() { - return null; - } - - @Override - public SocketAddress getClientAddress() { - return null; - } - } -} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java index 8a92987c..0e132b5b 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java @@ -2,6 +2,7 @@ import com.tvd12.ezyfoxserver.constant.EzyTransportType; import com.tvd12.ezyfoxserver.socket.EzySimplePacket; +import com.tvd12.test.assertion.Asserts; import org.testng.annotations.Test; public class EzySimplePacketTest { @@ -21,4 +22,18 @@ public void test() { assert packet.getSize() == "hello".length(); System.out.println(packet); } + + @Test + public void replaceDataTest() { + // given + EzySimplePacket packet = new EzySimplePacket(); + packet.setData("hello"); + + + // when + packet.replaceData("world"); + + // then + Asserts.assertEquals(packet.getData(), "world"); + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java deleted file mode 100644 index 050e6b70..00000000 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySocketChannelsTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.tvd12.ezyfoxserver.testing.socket; - -import com.tvd12.ezyfoxserver.socket.EzySocketChannels; -import com.tvd12.test.assertion.Asserts; -import org.testng.annotations.Test; - -import javax.net.ssl.SSLException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -public class EzySocketChannelsTest { - - @Test - public void writeTimeOut() { - // given - long timeoutAt = System.currentTimeMillis() - 1000; - SocketChannel socketChannel = mock(SocketChannel.class); - ByteBuffer buffer = ByteBuffer.allocate(2); - buffer.put(new byte[] {1, 2}); - - // when - Throwable e = Asserts.assertThrows(() -> - EzySocketChannels.write( - socketChannel, - buffer, - timeoutAt - ) - ); - - // then - Asserts.assertEqualsType(e, SSLException.class); - - verifyNoMoreInteractions(socketChannel); - } -} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java deleted file mode 100644 index 3c76e0ae..00000000 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslEnginesTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.tvd12.ezyfoxserver.testing.ssl; - -import org.testng.annotations.Test; - -import javax.net.ssl.SSLEngine; - -import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; -import static org.mockito.Mockito.*; - -public class EzySslEnginesTest { - - @Test - public void safeCloseOutboundTest() { - // given - SSLEngine engine = mock(SSLEngine.class); - - // when - safeCloseOutbound(engine); - - // then - verify(engine, times(1)).closeOutbound(); - } - - @Test - public void safeCloseOutboundCaseExceptionTest() { - // given - SSLEngine engine = mock(SSLEngine.class); - RuntimeException error = new RuntimeException("test"); - doThrow(error).when(engine).closeOutbound(); - - // when - safeCloseOutbound(engine); - - // then - verify(engine, times(1)).closeOutbound(); - } -} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java deleted file mode 100644 index 1808b7c9..00000000 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySslHandshakeHandlerTest.java +++ /dev/null @@ -1,824 +0,0 @@ -package com.tvd12.ezyfoxserver.testing.ssl; - -import com.tvd12.ezyfox.io.EzyByteBuffers; -import com.tvd12.ezyfox.util.EzyThreads; -import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; -import com.tvd12.test.assertion.Asserts; -import com.tvd12.test.reflect.FieldUtil; -import com.tvd12.test.util.RandomUtil; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import javax.net.ssl.*; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.mockito.Mockito.*; - -public class EzySslHandshakeHandlerTest { - - private SSLContext sslContext; - private EzySSLContextSpiForTest sslContextSpi; - private SSLEngine sslEngine; - private SSLSession sslSession; - private SocketChannel socketChannel; - private final int timeout = 50; - private final int bufferSize = 128; - private EzySslHandshakeHandler instance; - - @BeforeMethod - public void setup() { - this.sslContext = mock(SSLContext.class); - this.sslContextSpi = mock(EzySSLContextSpiForTest.class); - this.sslEngine = mock(SSLEngine.class); - this.sslSession = mock(SSLSession.class); - this.socketChannel = mock(SocketChannel.class); - this.instance = new EzySslHandshakeHandler( - sslContext, - timeout - ); - FieldUtil.setFieldValue( - sslContext, - "contextSpi", - sslContextSpi - ); - when(sslContextSpi.engineCreateSSLEngine()).thenReturn(sslEngine); - when(sslEngine.getSession()).thenReturn(sslSession); - when(sslSession.getApplicationBufferSize()).thenReturn(bufferSize); - when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); - } - - @AfterMethod - public void verifyAll() throws Exception { - verifyNoMoreInteractions(sslContext); - - verify(sslContextSpi, times(1)).engineCreateSSLEngine(); - verifyNoMoreInteractions(sslContextSpi); - - verify(sslEngine, times(1)).beginHandshake(); - verify(sslEngine, times(1)).setUseClientMode(false); - verify(sslEngine, times(1)).getSession(); - verifyNoMoreInteractions(sslEngine); - - verify(sslSession, times(1)).getPacketBufferSize(); - verify(sslSession, times(1)).getApplicationBufferSize(); - verifyNoMoreInteractions(sslSession); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsOk() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NEED_UNWRAP - ); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenReturn(engineResult); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - }); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)).isInboundDone(); - verify(sslEngine, times(1)).closeInbound(); - verify(sslEngine, times(1)).closeOutbound(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0CloseInboundError() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - }); - - SSLException error = new SSLException("test"); - doThrow(error).when(sslEngine).closeInbound(); - - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)).isInboundDone(); - verify(sslEngine, times(1)).closeInbound(); - verify(sslEngine, times(1)).closeOutbound(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0OutboundNotDone() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - }); - - when(sslEngine.isInboundDone()).thenReturn(true); - - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)).isInboundDone(); - verify(sslEngine, times(1)).isOutboundDone(); - verify(sslEngine, times(1)).closeInbound(); - verify(sslEngine, times(1)).closeOutbound(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0InOutboundDone() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; - }); - - when(sslEngine.isInboundDone()).thenReturn(true); - when(sslEngine.isOutboundDone()).thenReturn(true); - - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); - - // when - Throwable e = Asserts.assertThrows(() -> - instance.handle(socketChannel) - ); - - // then - Asserts.assertEqualsType(e, SSLException.class); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)).isInboundDone(); - verify(sslEngine, times(1)).isOutboundDone(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapEngineUnwrapThrowException() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); - - SSLException error = new SSLException("test"); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenThrow(error); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - verify(sslEngine, times(1)).closeOutbound(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferOverflow() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); - - SSLEngineResult engineResultOverflow = new SSLEngineResult( - SSLEngineResult.Status.BUFFER_OVERFLOW, - SSLEngineResult.HandshakeStatus.NEED_UNWRAP, - 0, - 0 - ); - SSLEngineResult engineResultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - AtomicInteger unwrapCallCount = new AtomicInteger(); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - int callCount = unwrapCallCount.incrementAndGet(); - if (callCount == 1) { - return engineResultOverflow; - } - return engineResultOk; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(2)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(2)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferUnderFlow() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); - - SSLEngineResult engineResultOverflow = new SSLEngineResult( - SSLEngineResult.Status.BUFFER_UNDERFLOW, - SSLEngineResult.HandshakeStatus.NEED_UNWRAP, - 0, - 0 - ); - SSLEngineResult engineResultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - AtomicInteger unwrapCallCount = new AtomicInteger(); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - int callCount = unwrapCallCount.incrementAndGet(); - if (callCount == 1) { - return engineResultOverflow; - } - return engineResultOk; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(2)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(2)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosed() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); - - SSLEngineResult engineResultOverflow = new SSLEngineResult( - SSLEngineResult.Status.CLOSED, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - SSLEngineResult engineResultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - AtomicInteger unwrapCallCount = new AtomicInteger(); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - int callCount = unwrapCallCount.incrementAndGet(); - if (callCount == 1) { - return engineResultOverflow; - } - return engineResultOk; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - verify(sslEngine, times(1)).isOutboundDone(); - verify(sslEngine, times(1)).closeOutbound(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosedOutboundDone() throws Exception { - // given - when(sslEngine.isOutboundDone()).thenReturn(true); - - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); - - SSLEngineResult engineResultOverflow = new SSLEngineResult( - SSLEngineResult.Status.CLOSED, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - SSLEngineResult engineResultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - AtomicInteger unwrapCallCount = new AtomicInteger(); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - int callCount = unwrapCallCount.incrementAndGet(); - if (callCount == 1) { - return engineResultOverflow; - } - return engineResultOk; - }); - - // when - Throwable e = Asserts.assertThrows(() -> - instance.handle(socketChannel) - ); - - // then - Asserts.assertEqualsType(e, SSLException.class); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - verify(sslEngine, times(1)).isOutboundDone(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsOk() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NEED_WRAP - ); - when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); - EzyByteBuffers.getBytes(buffer); - return bufferSize; - }); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - when( - sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); - buffer.clear(); - buffer.put(new byte[] {1, 2, 3}); - return engineResult; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .write(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedWrapEngineThrowException() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_WRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - SSLException error = new SSLException("test"); - when( - sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenThrow(error); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); - verify(sslEngine, times(1)).closeOutbound(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsBufferOverflow() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NEED_WRAP - ); - when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); - EzyByteBuffers.getBytes(buffer); - return bufferSize; - }); - - SSLEngineResult engineResultBufferOverflow = new SSLEngineResult( - SSLEngineResult.Status.BUFFER_OVERFLOW, - SSLEngineResult.HandshakeStatus.NEED_WRAP, - 0, - 0 - ); - SSLEngineResult engineResultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - AtomicInteger wrapCallCount = new AtomicInteger(); - when( - sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - int callCount = wrapCallCount.incrementAndGet(); - if (callCount == 1) { - return engineResultBufferOverflow; - } - ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); - buffer.clear(); - buffer.put(new byte[] {1, 2, 3}); - return engineResultOk; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .write(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(2)) - .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsBufferUnderflow() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NEED_WRAP - ); - when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); - EzyByteBuffers.getBytes(buffer); - return bufferSize; - }); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.BUFFER_UNDERFLOW, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - when( - sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenReturn(engineResult); - - // when - Throwable e = Asserts.assertThrows(() -> - instance.handle(socketChannel) - ); - - // then - Asserts.assertEqualsType(e, SSLException.class); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosed() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NEED_WRAP - ); - when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); - EzyByteBuffers.getBytes(buffer); - return bufferSize; - }); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.CLOSED, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - when( - sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); - buffer.clear(); - buffer.put(new byte[] {1, 2, 3}); - return engineResult; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .write(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteThrowException() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_WRAP; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - IOException error = new IOException("test"); - when(socketChannel.write(any(ByteBuffer.class))).thenThrow(error); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.CLOSED, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); - when( - sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenAnswer(it -> { - ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); - buffer.clear(); - buffer.put(new byte[] {1, 2, 3}); - return engineResult; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(socketChannel, times(1)) - .write(any(ByteBuffer.class)); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void handleCaseHandshakeStatusIsNeedTask() throws Exception { - // given - AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); - when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { - int callCount = getHandshakeStatusCallCount.incrementAndGet(); - if (callCount == 1) { - return SSLEngineResult.HandshakeStatus.NEED_TASK; - } - return SSLEngineResult.HandshakeStatus.FINISHED; - }); - Runnable task = mock(Runnable.class); - AtomicInteger getDelegatedTaskCallCount = new AtomicInteger(); - when(sslEngine.getDelegatedTask()).thenAnswer(it -> { - int callCount = getDelegatedTaskCallCount.incrementAndGet(); - if (callCount == 1) { - return task; - } - return null; - }); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(sslEngine, times(2)).getHandshakeStatus(); - verify(sslEngine, times(2)).getDelegatedTask(); - - verify(task, times(1)).run(); - verifyNoMoreInteractions(task); - } - - @Test - public void handleCaseHandshakeStatusIsFinish() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.FINISHED - ); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(sslEngine, times(1)).getHandshakeStatus(); - } - - @Test - public void handleCaseHandshakeStatusIsNotHandshake() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING - ); - - // when - SSLEngine actual = instance.handle(socketChannel); - - // then - Asserts.assertEquals(actual, sslEngine); - - verify(sslEngine, times(1)).getHandshakeStatus(); - } - - @Test - public void handleCaseHandshakeStatusIsNeedUnwrapTimeout() throws Exception { - // given - when(sslEngine.getHandshakeStatus()).thenReturn( - SSLEngineResult.HandshakeStatus.NEED_UNWRAP - ); - int readBytes = RandomUtil.randomSmallInt(); - when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(it -> { - EzyThreads.sleep(timeout * 2); - return readBytes; - }); - - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.NEED_WRAP, - 0, - 0 - ); - when( - sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) - ).thenReturn(engineResult); - - // when - Throwable e = Asserts.assertThrows(() -> - instance.handle(socketChannel) - ); - - // then - Asserts.assertEqualsType(e, SSLException.class); - - verify(socketChannel, times(1)) - .read(any(ByteBuffer.class)); - - verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } -} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java deleted file mode 100644 index 3f0cd51c..00000000 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/SslByteBuffersTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.tvd12.ezyfoxserver.testing.ssl; - -import com.tvd12.test.assertion.Asserts; -import com.tvd12.test.util.RandomUtil; -import org.testng.annotations.Test; - -import java.nio.ByteBuffer; - -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; - -public class SslByteBuffersTest { - - @Test - public void enlargeBufferTest() { - // given - ByteBuffer buffer = ByteBuffer.allocate(1); - int sessionProposedCapacity = RandomUtil.randomSmallInt() + 2; - - // when - ByteBuffer actual = enlargeBuffer( - buffer, - sessionProposedCapacity - ); - - // then - Asserts.assertEquals(actual.capacity(), sessionProposedCapacity); - } - - @Test - public void enlargeBufferIfNeedTest() { - // given - ByteBuffer buffer = ByteBuffer.allocate(3); - buffer.put(new byte[] {1, 2}); - buffer.flip(); - - // when - ByteBuffer actual = enlargeBufferIfNeed( - buffer, - 1 - ); - - // then - Asserts.assertEquals(actual, buffer); - } -} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index d4568a4f..8c701670 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java @@ -15,9 +15,7 @@ import com.tvd12.ezyfoxserver.nio.wrapper.impl.EzyHandlerGroupManagerImpl; import com.tvd12.ezyfoxserver.setting.EzySocketSetting; import com.tvd12.ezyfoxserver.socket.*; -import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; -import javax.net.ssl.SSLContext; import java.util.concurrent.ExecutorService; public class EzyNioServerBootstrapBuilderImpl @@ -47,10 +45,8 @@ protected EzyServerBootstrap newServerBootstrap() { EzyHandlerGroupManager handlerGroupManager = newHandlerGroupManager( handlerGroupBuilderFactory ); - SSLContext sslContext = newSslContext(getWebsocketSetting().getSslConfig()); EzySocketDataReceiver socketDataReceiver = newSocketDataReceiver( - handlerGroupManager, - sslContext + handlerGroupManager ); EzyNioServerBootstrap bootstrap = new EzyNioServerBootstrap(); bootstrap.setResponseApi(responseApi); @@ -62,7 +58,7 @@ protected EzyServerBootstrap newServerBootstrap() { bootstrap.setSocketSessionTicketsQueue(socketSessionTicketsQueue); bootstrap.setWebsocketSessionTicketsQueue(websocketSessionTicketsQueue); bootstrap.setSocketSessionTicketsRequestQueues(sessionTicketsRequestQueues); - bootstrap.setSslContext(sslContext); + bootstrap.setSslContext(newSslContext(getWebsocketSetting().getSslConfig())); return bootstrap; } @@ -113,28 +109,18 @@ private ExecutorService newStatsThreadPool() { } private EzySocketDataReceiver newSocketDataReceiver( - EzyHandlerGroupManager handlerGroupManager, - SSLContext sslContext + EzyHandlerGroupManager handlerGroupManager ) { - return newSocketDataReceiverBuilder(sslContext) + return newSocketDataReceiverBuilder() .handlerGroupManager(handlerGroupManager) .threadPoolSize(getThreadPoolSizeSetting().getSocketDataReceiver()) .build(); } - private EzySocketDataReceiver.Builder newSocketDataReceiverBuilder( - SSLContext sslContext - ) { + private EzySocketDataReceiver.Builder newSocketDataReceiverBuilder() { EzySocketSetting setting = getSocketSetting(); return setting.isCertificationSslActive() - ? EzySecureSocketDataReceiver - .builder() - .sslHandshakeHandler( - new EzySslHandshakeHandler( - sslContext, - setting.getSslHandshakeTimeout() - ) - ) + ? EzySecureSocketDataReceiver.builder() : EzySocketDataReceiver.builder(); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 9b00e2e7..986ec3bc 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -11,9 +11,6 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import static com.tvd12.ezyfoxserver.ssl.EzySslEngines.safeCloseOutbound; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBuffer; -import static com.tvd12.ezyfoxserver.ssl.SslByteBuffers.enlargeBufferIfNeed; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; @@ -194,7 +191,7 @@ public byte[] read(ByteBuffer buffer) throws Exception { tcpAppBuffer = enlargeBufferIfNeed(tcpAppBuffer, netBufferSize); break; case CLOSED: - safeCloseOutbound(engine); + safeCloseOutbound(); throw new EzyConnectionCloseException( "ssl unwrap result status is CLOSE" ); @@ -210,6 +207,9 @@ public byte[] read(ByteBuffer buffer) throws Exception { @Override public byte[] pack(byte[] bytes) throws Exception { + if (!handshaked) { + throw new SSLException("not handshaked"); + } ByteBuffer buffer = ByteBuffer.wrap(bytes); ByteBuffer netBuffer = ByteBuffer.allocate(netBufferSize); while (buffer.hasRemaining()) { @@ -224,7 +224,7 @@ public byte[] pack(byte[] bytes) throws Exception { case BUFFER_UNDERFLOW: throw new IOException("Buffer underflow occurred after a wrap"); case CLOSED: - safeCloseOutbound(engine); + safeCloseOutbound(); throw new EzyConnectionCloseException( "ssl wrap result status is CLOSE" ); @@ -252,4 +252,38 @@ public static void writeOrTimeout( channel.write(buffer); } } + + private void safeCloseOutbound() { + try { + engine.closeOutbound(); + } catch (Throwable e) { + logger.info("close outbound error", e); + } + } + + private ByteBuffer enlargeBuffer( + ByteBuffer buffer, + int sessionProposedCapacity + ) { + return sessionProposedCapacity > buffer.capacity() + ? ByteBuffer.allocate(sessionProposedCapacity) + : ByteBuffer.allocate(buffer.capacity() * 2); + } + + private ByteBuffer enlargeBufferIfNeed( + ByteBuffer buffer, + int packageBufferSize + ) { + if (packageBufferSize < buffer.limit()) { + return buffer; + } else { + ByteBuffer replaceBuffer = enlargeBuffer( + buffer, + packageBufferSize + ); + buffer.flip(); + replaceBuffer.put(buffer); + return replaceBuffer; + } + } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 67d6dc65..423d1877 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -3,13 +3,10 @@ import com.tvd12.ezyfoxserver.constant.EzyDisconnectReason; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.socket.EzyChannel; -import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import static com.tvd12.ezyfoxserver.constant.EzyCoreConstants.MAX_SECURE_READ_BUFFER_SIZE; - public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { public EzySecureSocketDataReceiver(Builder builder) { @@ -51,24 +48,12 @@ protected byte[] readTcpBytesFromBuffer( return secureChannel.read(buffer); } - @Override - protected int getMaxBufferSize() { - return MAX_SECURE_READ_BUFFER_SIZE; - } - public static Builder builder() { return new Builder(); } public static class Builder extends EzySocketDataReceiver.Builder { - protected EzySslHandshakeHandler sslHandshakeHandler; - - public Builder sslHandshakeHandler(EzySslHandshakeHandler sslHandshakeHandler) { - this.sslHandshakeHandler = sslHandshakeHandler; - return this; - } - @Override public EzySocketDataReceiver build() { return new EzySecureSocketDataReceiver(this); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java index daf363c8..40f3b839 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java @@ -3,11 +3,11 @@ import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.context.EzyServerContext; import com.tvd12.ezyfoxserver.nio.EzySocketServerBootstrap; +import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketAcceptor; import com.tvd12.ezyfoxserver.nio.socket.EzyNioSocketAcceptor; import com.tvd12.ezyfoxserver.setting.EzySettings; import com.tvd12.ezyfoxserver.setting.EzySocketSetting; import com.tvd12.test.assertion.Asserts; -import com.tvd12.test.reflect.FieldUtil; import com.tvd12.test.reflect.MethodInvoker; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; @@ -55,6 +55,8 @@ public void verifyAll() { verify(settings, times(1)).getSocket(); verifyNoMoreInteractions(settings); + verify(socketSetting, times(1)).isCertificationSslActive(); + verify(socketSetting, times(1)).getSslHandshakeTimeout(); verifyNoMoreInteractions(socketSetting); verifyNoMoreInteractions(sslContext); } @@ -63,7 +65,6 @@ public void verifyAll() { public void newSocketAcceptorCaseCertificationSsl() { // given when(socketSetting.isCertificationSslActive()).thenReturn(true); - when(socketSetting.getSslConnectionAcceptorThreadPoolSize()).thenReturn(1); // when EzyNioSocketAcceptor acceptor = MethodInvoker.create() @@ -72,15 +73,9 @@ public void newSocketAcceptorCaseCertificationSsl() { .invoke(EzyNioSocketAcceptor.class); // then - Asserts.assertNotNull( - FieldUtil.getFieldValue( - acceptor, - "sslHandshakeHandler" - ) - ); + Asserts.assertEqualsType(acceptor, EzyNioSecureSocketAcceptor.class); verify(socketSetting, times(1)).isCertificationSslActive(); - verify(socketSetting, times(1)).getSslConnectionAcceptorThreadPoolSize(); verify(socketSetting, times(1)).getSslHandshakeTimeout(); } } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index 38cbab93..ad80721e 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java @@ -446,7 +446,7 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualUdp() throws EzyPacket packet = mock(EzyPacket.class); when( packet.getTransportType() - ).thenReturn(EzyTransportType.UDP_OR_TCP); + ).thenReturn(EzyTransportType.UDP); when(packet.getData()).thenReturn(new byte[]{1, 2, 3}); ByteBuffer buffer = ByteBuffer.allocate(100); @@ -463,7 +463,6 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualUdp() throws // then verify(session, times(1)).isActivated(); verify(session, times(2)).getChannel(); - verify(session, times(2)).getDatagramChannelPool(); verify(session, times(1)).getUdpClientAddress(); } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java index 1507dedd..b8cec4f4 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java @@ -1,16 +1,17 @@ package com.tvd12.ezyfoxserver.nio.testing.socket; import com.tvd12.ezyfox.io.EzyByteBuffers; +import com.tvd12.ezyfox.util.EzyThreads; import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketChannel; import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.FieldUtil; +import com.tvd12.test.util.RandomUtil; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLSession; +import javax.net.ssl.*; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; @@ -22,43 +23,853 @@ public class EzyNioSecureSocketChannelTest { private byte[] bytes; + private ByteBuffer buffer; + private ByteBuffer netBuffer; private SSLEngine sslEngine; + private SSLContext sslContext; + private EzySSLContextSpiForTest sslContextSpi; private SSLSession sslSession; private SocketChannel socketChannel; private EzyNioSecureSocketChannel instance; + private final int bufferSize = 128; + private static final int sslHandshakeTimeout = 100; @BeforeMethod public void setup() { - int bufferSize = 128; bytes = new byte[] {1, 2, 3}; + buffer = ByteBuffer.allocate(bufferSize); + netBuffer = ByteBuffer.allocate(bufferSize); + sslContext = mock(SSLContext.class); + sslContextSpi = mock(EzySSLContextSpiForTest.class); sslEngine = mock(SSLEngine.class); sslSession = mock(SSLSession.class); socketChannel = mock(SocketChannel.class); instance = new EzyNioSecureSocketChannel( - socketChannel + socketChannel, + sslContext, + sslHandshakeTimeout ); + FieldUtil.setFieldValue( + sslContext, + "contextSpi", + sslContextSpi + ); + when(sslContextSpi.engineCreateSSLEngine()).thenReturn(sslEngine); when(sslEngine.getSession()).thenReturn(sslSession); when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); + when(sslSession.getApplicationBufferSize()).thenReturn(bufferSize); + } + + public void beforeNotHandshakeMethod() { + FieldUtil.setFieldValue(instance, "engine", sslEngine); + FieldUtil.setFieldValue(instance, "netBuffer", netBuffer); + FieldUtil.setFieldValue(instance, "handshaked", true); + FieldUtil.setFieldValue(instance, "appBufferSize", bufferSize); + FieldUtil.setFieldValue(instance, "netBufferSize", bufferSize); } @AfterMethod public void verifyAll() throws Exception { - Asserts.assertEquals(instance.getEngine(), sslEngine); - - verify(sslEngine, times(1)).getSession(); + verifyNoMoreInteractions(sslContext); + verifyNoMoreInteractions(sslContextSpi); verifyNoMoreInteractions(sslEngine); - - verify(sslSession, times(1)).getPacketBufferSize(); verifyNoMoreInteractions(sslSession); - verify(socketChannel, times(1)).getLocalAddress(); verify(socketChannel, times(1)).getRemoteAddress(); verifyNoMoreInteractions(socketChannel); } + private void verifyAfterHandshake() throws Exception { + verify(sslContextSpi, times(1)).engineCreateSSLEngine(); + + verify(sslEngine, times(1)).getSession(); + verify(sslEngine, times(1)).setUseClientMode(false); + verify(sslEngine, times(1)).beginHandshake(); + + verify(sslSession, times(1)).getApplicationBufferSize(); + verify(sslSession, times(1)).getPacketBufferSize(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsOk() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_UNWRAP + ); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenReturn(engineResult); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).closeInbound(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0CloseInboundError() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + + SSLException error = new SSLException("test"); + doThrow(error).when(sslEngine).closeInbound(); + + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).closeInbound(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0OutboundNotDone() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + + when(sslEngine.isInboundDone()).thenReturn(true); + + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).isOutboundDone(); + verify(sslEngine, times(1)).closeInbound(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapReadBytesLt0InOutboundDone() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + }); + + when(sslEngine.isInboundDone()).thenReturn(true); + when(sslEngine.isOutboundDone()).thenReturn(true); + + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(-1); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handshake() + ); + + // then + verifyAfterHandshake(); + Asserts.assertEqualsType(e, SSLException.class); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)).isInboundDone(); + verify(sslEngine, times(1)).isOutboundDone(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineUnwrapThrowException() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLException error = new SSLException("test"); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenThrow(error); + + // when + instance.handshake(); + + // then + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferOverflow() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NEED_UNWRAP, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(2)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferUnderFlow() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NEED_UNWRAP, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(2)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosed() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).isOutboundDone(); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsClosedOutboundDone() throws Exception { + // given + when(sslEngine.isOutboundDone()).thenReturn(true); + + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenReturn(readBytes); + + SSLEngineResult engineResultOverflow = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger unwrapCallCount = new AtomicInteger(); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultOverflow; + } + return engineResultOk; + }); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handshake() + ); + + // then + verifyAfterHandshake(); + + Asserts.assertEqualsType(e, SSLException.class); + + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).isOutboundDone(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsOk() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineThrowException() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + SSLException error = new SSLException("test"); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenThrow(error); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsBufferOverflow() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NEED_WRAP, + 0, + 0 + ); + SSLEngineResult engineResultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + AtomicInteger wrapCallCount = new AtomicInteger(); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + int callCount = wrapCallCount.incrementAndGet(); + if (callCount == 1) { + return engineResultBufferOverflow; + } + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResultOk; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(2)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsBufferUnderflow() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenReturn(engineResult); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handshake() + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verifyAfterHandshake(); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosed() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(0, ByteBuffer.class); + EzyByteBuffers.getBytes(buffer); + return bufferSize; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteThrowException() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + IOException error = new IOException("test"); + when(socketChannel.write(any(ByteBuffer.class))).thenThrow(error); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.FINISHED, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void handleCaseHandshakeStatusIsNeedTask() throws Exception { + // given + AtomicInteger getHandshakeStatusCallCount = new AtomicInteger(); + when(sslEngine.getHandshakeStatus()).thenAnswer(it -> { + int callCount = getHandshakeStatusCallCount.incrementAndGet(); + if (callCount == 1) { + return SSLEngineResult.HandshakeStatus.NEED_TASK; + } + return SSLEngineResult.HandshakeStatus.FINISHED; + }); + Runnable task = mock(Runnable.class); + AtomicInteger getDelegatedTaskCallCount = new AtomicInteger(); + when(sslEngine.getDelegatedTask()).thenAnswer(it -> { + int callCount = getDelegatedTaskCallCount.incrementAndGet(); + if (callCount == 1) { + return task; + } + return null; + }); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(sslEngine, times(2)).getHandshakeStatus(); + verify(sslEngine, times(2)).getDelegatedTask(); + + verify(task, times(1)).run(); + verifyNoMoreInteractions(task); + } + + @Test + public void handleCaseHandshakeStatusIsFinish() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.FINISHED + ); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(sslEngine, times(1)).getHandshakeStatus(); + } + + @Test + public void handleCaseHandshakeStatusIsNotHandshake() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING + ); + + // when + instance.handshake(); + + // then + Asserts.assertTrue(instance.isHandshaked()); + + verifyAfterHandshake(); + verify(sslEngine, times(1)).getHandshakeStatus(); + } + + @Test + public void handleCaseHandshakeStatusIsNeedUnwrapTimeout() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_UNWRAP + ); + int readBytes = RandomUtil.randomSmallInt(); + when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(it -> { + EzyThreads.sleep(sslHandshakeTimeout * 2); + return readBytes; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NEED_WRAP, + 0, + 0 + ); + when( + sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenReturn(engineResult); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handshake() + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .read(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + @Test public void packCaseOk() throws Exception { // given + beforeNotHandshakeMethod(); + SSLEngineResult result = new SSLEngineResult( SSLEngineResult.Status.OK, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, @@ -85,8 +896,10 @@ public void packCaseOk() throws Exception { } @Test - public void readTcpBytesFromBufferCaseBufferOverFlow() throws Exception { + public void packCaseBufferOverFlow() throws Exception { // given + beforeNotHandshakeMethod(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( SSLEngineResult.Status.BUFFER_OVERFLOW, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, @@ -124,8 +937,10 @@ public void readTcpBytesFromBufferCaseBufferOverFlow() throws Exception { } @Test - public void readTcpBytesFromBufferCaseBufferUnderFlow() throws Exception { + public void packCaseBufferUnderFlow() throws Exception { // given + beforeNotHandshakeMethod(); + SSLEngineResult result = new SSLEngineResult( SSLEngineResult.Status.BUFFER_UNDERFLOW, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, @@ -149,8 +964,10 @@ public void readTcpBytesFromBufferCaseBufferUnderFlow() throws Exception { } @Test - public void readTcpBytesFromBufferCaseBufferClosed() throws Exception { + public void packCaseBufferClosed() throws Exception { // given + beforeNotHandshakeMethod(); + SSLEngineResult result = new SSLEngineResult( SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, @@ -175,11 +992,177 @@ public void readTcpBytesFromBufferCaseBufferClosed() throws Exception { } @Test - public void readTcpBytesFromBufferNoRemaining() throws Exception { + public void packNoRemaining() throws Exception { // when + beforeNotHandshakeMethod(); + byte[] actual = instance.pack(new byte[0]); // then Asserts.assertEquals(actual, new byte[0]); } + + @Test + public void readCaseOk() throws Exception { + // given + beforeNotHandshakeMethod(); + + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(netBuffer); + return result; + }); + + // when + byte[] actual = instance.read(buffer); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readCaseBufferOverFlow() throws Exception { + // given + beforeNotHandshakeMethod(); + + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger unwrapCallCount = new AtomicInteger(); + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(netBuffer); + return resultOk; + }); + + // when + byte[] actual = instance.read(buffer); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readCaseBufferUnderFlow() throws Exception { + // given + beforeNotHandshakeMethod(); + + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger unwrapCallCount = new AtomicInteger(); + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(netBuffer); + return resultOk; + }); + + // when + byte[] actual = instance.read(buffer); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readCaseBufferClosed() throws Exception { + // given + beforeNotHandshakeMethod(); + + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.read(buffer) + ); + + // then + Asserts.assertEqualsType(e, EzyConnectionCloseException.class); + + verify(sslEngine, times(1)).closeOutbound(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + + @Test + public void readNoRemaining() throws Exception { + // given + beforeNotHandshakeMethod(); + + buffer.clear(); + buffer.flip(); + + // when + byte[] actual = instance.read(buffer); + + // then + Asserts.assertEquals(actual, new byte[0]); + } } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java index bf980ae5..dedd218d 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketAcceptorTest.java @@ -7,6 +7,7 @@ import com.tvd12.ezyfoxserver.EzySimpleServer; import com.tvd12.ezyfoxserver.codec.EzyCodecFactory; import com.tvd12.ezyfoxserver.constant.EzyCommand; +import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.context.EzySimpleServerContext; import com.tvd12.ezyfoxserver.nio.builder.impl.EzyHandlerGroupBuilderFactoryImpl; import com.tvd12.ezyfoxserver.nio.entity.EzyNioSession; @@ -22,17 +23,13 @@ import com.tvd12.ezyfoxserver.setting.EzySimpleSettings; import com.tvd12.ezyfoxserver.setting.EzySimpleStreamingSetting; import com.tvd12.ezyfoxserver.socket.*; -import com.tvd12.ezyfoxserver.ssl.EzySslHandshakeHandler; import com.tvd12.ezyfoxserver.statistics.EzySimpleStatistics; import com.tvd12.ezyfoxserver.statistics.EzyStatistics; -import com.tvd12.test.assertion.Asserts; import com.tvd12.test.base.BaseTest; import com.tvd12.test.reflect.FieldUtil; import com.tvd12.test.reflect.MethodInvoker; import org.testng.annotations.Test; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; @@ -74,7 +71,7 @@ public void test() throws Exception { channel1.configureBlocking(false); when(channel1.register(readSelector, SelectionKey.OP_READ)).thenReturn(selectionKey1); - EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); acceptor.setHandlerGroupManager(handlerGroupManager); acceptor.setOwnSelector(ownSelector); acceptor.setReadSelector(readSelector); @@ -88,9 +85,7 @@ public void test() throws Exception { @Test public void handleEventExceptionCase() throws Exception { // given - EzyNioSocketAcceptor instance = new EzyNioSocketAcceptor( - 1 - ); + EzyNioSocketAcceptor instance = new EzyNioSocketAcceptor(); Selector ownSelector = mock(Selector.class); IOException error = new IOException("test"); @@ -124,7 +119,7 @@ public void acceptConnectionExceptionCase() throws Exception { Selector readSelector = spy(ExSelector.class); - EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor acceptor = new EzyNioSocketAcceptor(); acceptor.setHandlerGroupManager(handlerGroupManager); acceptor.setOwnSelector(ownSelector); acceptor.setReadSelector(readSelector); @@ -187,7 +182,7 @@ public void acceptConnectionTest() throws Exception { EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); Selector readSelector = Selector.open(); - EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(); sut.setReadSelector(readSelector); SocketChannel clientChannel = SocketChannel.open(); @@ -225,7 +220,7 @@ public void acceptConnectionSslTest() throws Exception { EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); Selector readSelector = Selector.open(); - EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(); sut.setReadSelector(readSelector); SocketChannel clientChannel = SocketChannel.open(); @@ -235,12 +230,6 @@ public void acceptConnectionSslTest() throws Exception { EzyNioSession session = mock(EzyNioSession.class); when(handlerGroup.getSession()).thenReturn(session); - EzySslHandshakeHandler sslHandshakeHandler = - mock(EzySslHandshakeHandler.class); - SSLEngine sslEngine = mock(SSLEngine.class); - when(sslHandshakeHandler.handle(clientChannel)) - .thenReturn(sslEngine); - // when MethodInvoker.create() .object(sut) @@ -260,11 +249,6 @@ public void acceptConnectionSslTest() throws Exception { any(SelectionKey.class) ); verifyNoMoreInteractions(session); - - verify(sslHandshakeHandler, times(1)) - .handle(clientChannel); - verifyNoMoreInteractions(sslHandshakeHandler); - verifyNoMoreInteractions(sslEngine); } @Test @@ -274,7 +258,7 @@ public void acceptConnectionSslExceptionTest() throws Exception { EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); Selector readSelector = Selector.open(); - EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor sut = new EzyNioSocketAcceptor(); sut.setReadSelector(readSelector); SocketChannel clientChannel = SocketChannel.open(); @@ -284,13 +268,6 @@ public void acceptConnectionSslExceptionTest() throws Exception { EzyNioSession session = mock(EzyNioSession.class); when(handlerGroup.getSession()).thenReturn(session); - EzySslHandshakeHandler sslHandshakeHandler = - mock(EzySslHandshakeHandler.class); - SSLException error = new SSLException("test"); - when(sslHandshakeHandler.handle(clientChannel)) - .thenThrow(error); - sut.setSslHandshakeHandler(sslHandshakeHandler); - // when MethodInvoker.create() .object(sut) @@ -299,12 +276,16 @@ public void acceptConnectionSslExceptionTest() throws Exception { .call(); // then + verify(handlerGroupManager, times(1)) + .newHandlerGroup(any(EzyChannel.class), any(EzyConnectionType.class)); verifyNoMoreInteractions(handlerGroupManager); + + verify(handlerGroup, times(1)).getSession(); verifyNoMoreInteractions(handlerGroup); + + verify(session, times(1)) + .setProperty(any(String.class), any(SelectionKey.class)); verifyNoMoreInteractions(session); - verify(sslHandshakeHandler, times(1)) - .handle(clientChannel); - verifyNoMoreInteractions(sslHandshakeHandler); } public static abstract class ExServerSocketChannel extends ServerSocketChannel { diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java index 90d2fbe6..e680182d 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java @@ -99,7 +99,7 @@ public void test() throws Exception { when(socketChannel5.isConnected()).thenReturn(true); when(socketChannel5.read(any(ByteBuffer.class))).then((Answer) invocation -> -1); - EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); socketAcceptor.setReadSelector(ownSelector); socketAcceptor.setHandlerGroupManager(handlerGroupManager); @@ -136,7 +136,7 @@ public void testExceptionCase() throws Exception { throw new IllegalStateException("server maintain"); }); - EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(1); + EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); socketAcceptor.setReadSelector(ownSelector); socketAcceptor.setHandlerGroupManager(handlerGroupManager); diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySSLContextSpiForTest.java similarity index 80% rename from ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java rename to ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySSLContextSpiForTest.java index b481c9db..54ef5f39 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/ssl/EzySSLContextSpiForTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySSLContextSpiForTest.java @@ -1,4 +1,4 @@ -package com.tvd12.ezyfoxserver.testing.ssl; +package com.tvd12.ezyfoxserver.nio.testing.socket; import javax.net.ssl.SSLContextSpi; import javax.net.ssl.SSLEngine; diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java index 57e7001a..b0bc3048 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java @@ -1,6 +1,6 @@ package com.tvd12.ezyfoxserver.nio.testing.socket; -import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.ezyfoxserver.constant.EzyDisconnectReason; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketChannel; import com.tvd12.ezyfoxserver.nio.socket.EzySecureSocketDataReceiver; @@ -8,24 +8,20 @@ import com.tvd12.ezyfoxserver.socket.EzyChannel; import com.tvd12.test.assertion.Asserts; import com.tvd12.test.reflect.MethodInvoker; +import com.tvd12.test.util.RandomUtil; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; -import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Mockito.*; public class EzySecureSocketDataReceiverTest { private ByteBuffer buffer; - private SSLEngine sslEngine; - private SSLSession sslSession; private EzyNioSecureSocketChannel channel; private EzyHandlerGroupManager handlerGroupManager; private EzySecureSocketDataReceiver instance; @@ -34,8 +30,6 @@ public class EzySecureSocketDataReceiverTest { public void setup() { int bufferSize = 128; buffer = ByteBuffer.allocate(bufferSize); - sslEngine = mock(SSLEngine.class); - sslSession = mock(SSLSession.class); channel = mock(EzyNioSecureSocketChannel.class); handlerGroupManager = mock(EzyHandlerGroupManager.class); instance = (EzySecureSocketDataReceiver) EzySecureSocketDataReceiver @@ -43,192 +37,109 @@ public void setup() { .threadPoolSize(1) .handlerGroupManager(handlerGroupManager) .build(); - when(channel.getEngine()).thenReturn(sslEngine); - when(sslEngine.getSession()).thenReturn(sslSession); - when(sslSession.getApplicationBufferSize()).thenReturn(bufferSize); - when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); } @AfterMethod public void verifyAll() { - verify(sslEngine, times(1)).getSession(); - verifyNoMoreInteractions(sslEngine); - - verify(sslSession, times(1)).getApplicationBufferSize(); - verify(sslSession, times(1)).getPacketBufferSize(); - verifyNoMoreInteractions(sslSession); - - verify(channel, times(1)).getEngine(); verifyNoMoreInteractions(channel); verifyNoMoreInteractions(handlerGroupManager); } @Test - public void readTcpBytesFromBufferCaseOk() throws Exception { + public void tcpReadBytesNormalCase() throws Exception { // given - buffer.clear(); - buffer.put(new byte[] {1, 2}); - buffer.flip(); - SSLEngineResult result = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - - when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) - .thenAnswer(it -> { - ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); - tcpNetBuffer.clear(); - tcpNetBuffer.put(buffer); - return result; - }); + SocketChannel socketChannel = mock(SocketChannel.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + when(handlerGroupManager.getHandlerGroup(socketChannel)) + .thenReturn(handlerGroup); + when(handlerGroup.getChannel()).thenReturn(channel); // when - byte[] actual = MethodInvoker.create() + MethodInvoker.create() .object(instance) - .method("readTcpBytesFromBuffer") - .param(EzyChannel.class, channel) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) .param(ByteBuffer.class, buffer) - .invoke(byte[].class); + .invoke(); // then - Asserts.assertEquals(actual, new byte[] {1, 2}); + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); + + verify(handlerGroup, times(1)).getChannel(); + verifyNoMoreInteractions(handlerGroup); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(channel, times(1)).isHandshaked(); + verify(channel, times(1)).handshake(); } @Test - public void readTcpBytesFromBufferCaseBufferOverFlow() throws Exception { + public void tcpReadBytesHandshakeFailedCase() throws Exception { // given - buffer.clear(); - buffer.put(new byte[] {1, 2}); - buffer.flip(); - SSLEngineResult resultBufferOverflow = new SSLEngineResult( - SSLEngineResult.Status.BUFFER_OVERFLOW, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - SSLEngineResult resultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); + SocketChannel socketChannel = mock(SocketChannel.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + when(handlerGroupManager.getHandlerGroup(socketChannel)) + .thenReturn(handlerGroup); + when(handlerGroup.getChannel()).thenReturn(channel); - AtomicInteger unwrapCallCount = new AtomicInteger(); - when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) - .thenAnswer(it -> { - int callCount = unwrapCallCount.incrementAndGet(); - if (callCount == 1) { - return resultBufferOverflow; - } - ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); - tcpNetBuffer.clear(); - tcpNetBuffer.put(buffer); - return resultOk; - }); + SSLException exception = new SSLException("test"); + doThrow(exception).when(channel).handshake(); // when - byte[] actual = MethodInvoker.create() + MethodInvoker.create() .object(instance) - .method("readTcpBytesFromBuffer") - .param(EzyChannel.class, channel) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) .param(ByteBuffer.class, buffer) - .invoke(byte[].class); + .invoke(); // then - Asserts.assertEquals(actual, new byte[] {1, 2}); + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); + + verify(handlerGroup, times(1)).getChannel(); + verify(handlerGroup, times(1)).enqueueDisconnection( + EzyDisconnectReason.SSH_HANDSHAKE_FAILED + ); + verifyNoMoreInteractions(handlerGroup); - verify(sslEngine, times(2)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(channel, times(1)).isHandshaked(); + verify(channel, times(1)).handshake(); } @Test - public void readTcpBytesFromBufferCaseBufferUnderFlow() throws Exception { + public void tcpReadBytesAlreadyHandshakeCase() { // given - buffer.clear(); - buffer.put(new byte[] {1, 2}); - buffer.flip(); - SSLEngineResult resultBufferOverflow = new SSLEngineResult( - SSLEngineResult.Status.BUFFER_UNDERFLOW, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - SSLEngineResult resultOk = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - - AtomicInteger unwrapCallCount = new AtomicInteger(); - when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) - .thenAnswer(it -> { - int callCount = unwrapCallCount.incrementAndGet(); - if (callCount == 1) { - return resultBufferOverflow; - } - ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); - tcpNetBuffer.clear(); - tcpNetBuffer.put(buffer); - return resultOk; - }); + SocketChannel socketChannel = mock(SocketChannel.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + when(handlerGroupManager.getHandlerGroup(socketChannel)) + .thenReturn(handlerGroup); + when(handlerGroup.getChannel()).thenReturn(channel); + when(channel.isHandshaked()).thenReturn(true); // when - byte[] actual = MethodInvoker.create() + MethodInvoker.create() .object(instance) - .method("readTcpBytesFromBuffer") - .param(EzyChannel.class, channel) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) .param(ByteBuffer.class, buffer) - .invoke(byte[].class); + .invoke(); // then - Asserts.assertEquals(actual, new byte[] {1, 2}); - - verify(sslEngine, times(2)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - } - - @Test - public void readTcpBytesFromBufferCaseBufferClosed() throws Exception { - // given - SSLEngineResult result = new SSLEngineResult( - SSLEngineResult.Status.CLOSED, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - - when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) - .thenReturn(result); - - // when - Throwable e = Asserts.assertThrows(() -> - MethodInvoker.create() - .object(instance) - .method("readTcpBytesFromBuffer") - .param(EzyChannel.class, channel) - .param(ByteBuffer.class, buffer) - .invoke(byte[].class) - ); + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); - // then - Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); + verify(handlerGroup, times(1)).getChannel(); + verifyNoMoreInteractions(handlerGroup); - verify(sslEngine, times(1)).closeOutbound(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + verify(channel, times(1)).isHandshaked(); } @Test - public void readTcpBytesFromBufferNoRemaining() { + public void readTcpBytesFromBufferTest() throws Exception { // given - buffer.clear(); - buffer.flip(); + byte[] bytes = RandomUtil.randomShortByteArray(); + when(channel.read(buffer)).thenReturn(bytes); // when byte[] actual = MethodInvoker.create() @@ -239,53 +150,7 @@ public void readTcpBytesFromBufferNoRemaining() { .invoke(byte[].class); // then - Asserts.assertEquals(actual, new byte[0]); - } - - @Test - public void tcpReceiveCaseBufferClosed() throws Exception { - // given - SSLEngineResult result = new SSLEngineResult( - SSLEngineResult.Status.CLOSED, - SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, - 0, - 0 - ); - - when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) - .thenReturn(result); - - SocketChannel socketChannel = mock(SocketChannel.class); - byte[] bytes = new byte[] {1, 2, 3}; - when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(it -> { - ByteBuffer bf = it.getArgumentAt(0, ByteBuffer.class); - bf.clear(); - bf.put(bytes); - return bytes.length; - }); - - EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); - when(handlerGroupManager.getHandlerGroup(socketChannel)) - .thenReturn(handlerGroup); - when(handlerGroup.getChannel()).thenReturn(channel); - - // when - instance.tcpReceive(socketChannel); - Thread.sleep(300); - - // then - verify(handlerGroupManager, times(2)) - .getHandlerGroup(socketChannel); - - verify(socketChannel, times(1)).read(any(ByteBuffer.class)); - verifyNoMoreInteractions(socketChannel); - - verify(sslEngine, times(1)).closeOutbound(); - verify(sslEngine, times(1)) - .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); - - verify(handlerGroup, times(1)).getChannel(); - verify(handlerGroup, times(1)).enqueueDisconnection(); - verifyNoMoreInteractions(handlerGroup); + Asserts.assertEquals(actual, bytes); + verify(channel, times(1)).read(buffer); } } From f86c500812d5ffac211f2ccda4a62fa2a20384b2 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Thu, 10 Aug 2023 23:04:04 +0700 Subject: [PATCH 42/46] almost done unit test --- .../api/EzySecureSocketResponseApi.java | 8 +- .../EzyConnectionCloseException.java | 4 + .../ezyfoxserver/socket/EzySecureChannel.java | 2 +- .../api/EzyAbstractResponseApiTest.java | 34 +++- .../api/EzySecureProxyResponseApiTest.java | 39 ++++ .../api/EzySecureSocketResponseApiTest.java | 10 +- .../command/EzyCloseSessionImplTest.java | 22 ++- .../EzyConnectionCloseExceptionTest.java | 24 +++ .../nio/socket/EzyNioSecureSocketChannel.java | 71 ++++--- .../nio/socket/EzyNioSocketReader.java | 6 +- .../handler/EzySimpleNioHandlerGroupTest.java | 6 +- .../EzyNioSecureSocketAcceptorTest.java | 74 +++++++ .../socket/EzyNioSecureSocketChannelTest.java | 183 +++++++++++++++++- .../socket/EzyNioSocketReaderTest.java | 52 ++++- 14 files changed, 464 insertions(+), 71 deletions(-) create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureProxyResponseApiTest.java create mode 100644 ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/exception/EzyConnectionCloseExceptionTest.java create mode 100644 ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java index 0b59a8fb..537d7020 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java @@ -21,11 +21,11 @@ protected void sendTcpPacket( ) throws Exception { EzyChannel channel = session.getChannel(); if (channel == null) { - throw new IOException("session disconnected"); + throw new IOException("session destroyed"); } EzySecureChannel secureChannel = (EzySecureChannel) channel; try { - synchronized (secureChannel.getPackLock()) { + synchronized (secureChannel.getPackingLock()) { byte[] packedBytes = secureChannel.pack( (byte[]) packet.getData() ); @@ -45,11 +45,11 @@ protected void sendTcpPacketNow( ) throws Exception { EzyChannel channel = session.getChannel(); if (channel == null) { - throw new IOException("session disconnected"); + throw new IOException("session destroyed"); } EzySecureChannel secureChannel = (EzySecureChannel) channel; try { - synchronized (secureChannel.getPackLock()) { + synchronized (secureChannel.getPackingLock()) { byte[] packedBytes = secureChannel.pack( (byte[]) packet.getData() ); diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java index f5fd3c0c..b16a1866 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java @@ -7,4 +7,8 @@ public class EzyConnectionCloseException extends IOException { public EzyConnectionCloseException(String message) { super(message); } + + public EzyConnectionCloseException(String message, Throwable e) { + super(message, e); + } } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java index ec33f5dd..1cb0cfde 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java @@ -4,5 +4,5 @@ public interface EzySecureChannel { byte[] pack(byte[] bytes) throws Exception; - Object getPackLock(); + Object getPackingLock(); } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java index 23074535..0f37f080 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzyAbstractResponseApiTest.java @@ -377,7 +377,39 @@ public void secureResponseImmediateSendDueToCreatePacketException() throws Excep } @Test - public void secureResponseSendDueCreatePacketException() throws Exception { + public void secureResponseSendException() throws Exception { + // given + InternalResponseApi2 sut = new InternalResponseApi2(); + + EzyPackage pack = mock(EzyPackage.class); + when(pack.isEncrypted()).thenReturn(true); + + EzySession session = mock(EzySession.class); + RuntimeException error = new RuntimeException("test"); + doThrow(error).when(session).send(any(EzyPacket.class)); + + when(pack.getRecipients(EzyConnectionType.SOCKET)).thenReturn( + Collections.singleton(session) + ); + + // when + sut.response(pack, false); + + // then + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verify(pack, times(1)).getData(); + verify(pack, times(1)).getTransportType(); + + verifyNoMoreInteractions(pack); + + verify(session, times(1)).getSessionKey(); + verify(session, times(1)).send(any(EzyPacket.class)); + verifyNoMoreInteractions(session); + } + + @Test + public void secureResponseSendDueToCreatePacketException() throws Exception { // given InternalResponseApi2 sut = new CreatePackFailedInternalResponseApi2(); diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureProxyResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureProxyResponseApiTest.java new file mode 100644 index 00000000..7191e00b --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureProxyResponseApiTest.java @@ -0,0 +1,39 @@ +package com.tvd12.ezyfoxserver.testing.api; + +import com.tvd12.ezyfox.codec.EzyObjectToByteEncoder; +import com.tvd12.ezyfoxserver.api.EzySecureProxyResponseApi; +import com.tvd12.ezyfoxserver.codec.EzyCodecFactory; +import com.tvd12.ezyfoxserver.constant.EzyConnectionType; +import com.tvd12.ezyfoxserver.response.EzyPackage; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.*; + +public class EzySecureProxyResponseApiTest { + + @Test + public void responseTest() throws Exception { + // given + EzyCodecFactory codecFactory = mock(EzyCodecFactory.class); + EzyObjectToByteEncoder encoder = mock(EzyObjectToByteEncoder.class); + when(codecFactory.newEncoder(EzyConnectionType.SOCKET)).thenReturn(encoder); + + EzySecureProxyResponseApi api = new EzySecureProxyResponseApi(codecFactory); + EzyPackage pack = mock(EzyPackage.class); + when(pack.getTransportType()).thenReturn(EzyConnectionType.SOCKET); + + // when + api.response(pack); + + // then + verify(codecFactory, times(1)).newEncoder(EzyConnectionType.SOCKET); + verify(codecFactory, times(1)).newEncoder(EzyConnectionType.WEBSOCKET); + verifyNoMoreInteractions(codecFactory); + + verifyNoMoreInteractions(encoder); + + verify(pack, times(1)).isEncrypted(); + verify(pack, times(1)).getRecipients(EzyConnectionType.SOCKET); + verifyNoMoreInteractions(pack); + } +} diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java index 02b5a453..e8e4b9aa 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/api/EzySecureSocketResponseApiTest.java @@ -37,7 +37,7 @@ public void setup() { instance = new EzySecureSocketResponseApi(encoder); when(session.getChannel()).thenReturn(channel); - when(channel.getPackLock()).thenReturn(packLock); + when(channel.getPackingLock()).thenReturn(packLock); when(packet.getData()).thenReturn(data); } @@ -66,7 +66,7 @@ public void sendTcpPacketNormalCase() throws Exception { // then verify(channel, times(1)).pack(data); - verify(channel, times(1)).getPackLock(); + verify(channel, times(1)).getPackingLock(); verify(packet, times(1)).getData(); verify(packet, times(1)).replaceData(packedData); @@ -94,7 +94,7 @@ public void sendTcpPacketThrowCloseExceptionCase() throws Exception { Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); verify(channel, times(1)).pack(data); - verify(channel, times(1)).getPackLock(); + verify(channel, times(1)).getPackingLock(); verify(packet, times(1)).getData(); @@ -133,7 +133,7 @@ public void sendTcpPacketNowNormalCase() throws Exception { // then verify(channel, times(1)).pack(data); - verify(channel, times(1)).getPackLock(); + verify(channel, times(1)).getPackingLock(); verify(packet, times(1)).getData(); verify(packet, times(1)).replaceData(packedData); @@ -161,7 +161,7 @@ public void sendTcpPacketNowThrowCloseExceptionCase() throws Exception { Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); verify(channel, times(1)).pack(data); - verify(channel, times(1)).getPackLock(); + verify(channel, times(1)).getPackingLock(); verify(packet, times(1)).getData(); diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/command/EzyCloseSessionImplTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/command/EzyCloseSessionImplTest.java index 68a5c972..da2e15a8 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/command/EzyCloseSessionImplTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/command/EzyCloseSessionImplTest.java @@ -8,8 +8,7 @@ import com.tvd12.test.base.BaseTest; import org.testng.annotations.Test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.*; public class EzyCloseSessionImplTest extends BaseTest { @@ -32,4 +31,23 @@ public void noSendToClient() { session.setChannel(channel); cmd.close(session, EzyDisconnectReason.UNKNOWN); } + + @Test + public void noSendToClientDueToSshHandshakeFailed() { + // given + EzyAbstractSession session = mock(EzyAbstractSession.class); + + EzyServerContext serverContext = mock(EzyServerContext.class); + EzyCloseSessionImpl instance = new EzyCloseSessionImpl(serverContext); + + // when + instance.close(session, EzyDisconnectReason.SSH_HANDSHAKE_FAILED); + + // then + verifyNoMoreInteractions(serverContext); + + verify(session, times(1)).getClientAddress(); + verify(session, times(1)).close(); + verifyNoMoreInteractions(session); + } } diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/exception/EzyConnectionCloseExceptionTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/exception/EzyConnectionCloseExceptionTest.java new file mode 100644 index 00000000..afceeba6 --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/exception/EzyConnectionCloseExceptionTest.java @@ -0,0 +1,24 @@ +package com.tvd12.ezyfoxserver.testing.exception; + +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; +import com.tvd12.test.assertion.Asserts; +import org.testng.annotations.Test; + +public class EzyConnectionCloseExceptionTest { + + @Test + public void createByMsgAndThrowableTest() { + // given + Throwable e = new Throwable("test"); + + // when + EzyConnectionCloseException instance = new EzyConnectionCloseException( + "test", + e + ); + + // then + Asserts.assertEquals(instance.getMessage(), "test"); + Asserts.assertEquals(instance.getCause(), e); + } +} diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 986ec3bc..6ba310c7 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import java.util.concurrent.atomic.AtomicBoolean; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; @@ -20,14 +21,13 @@ public class EzyNioSecureSocketChannel private SSLEngine engine; private ByteBuffer netBuffer; - @Getter - private boolean handshaked; private int appBufferSize; private int netBufferSize; private final SSLContext sslContext; private final int sslHandshakeTimeout; @Getter - private final Object packLock = new Object(); + private final Object packingLock = new Object(); + private final AtomicBoolean handshaked = new AtomicBoolean(); private final Logger logger = LoggerFactory.getLogger(getClass()); public EzyNioSecureSocketChannel( @@ -40,6 +40,10 @@ public EzyNioSecureSocketChannel( this.sslHandshakeTimeout = sslHandshakeTimeout; } + public boolean isHandshaked() { + return handshaked.get(); + } + @SuppressWarnings("MethodLength") public void handshake() throws IOException { engine = sslContext.createSSLEngine(); @@ -108,7 +112,6 @@ public void handshake() throws IOException { peerAppData = enlargeBuffer(peerAppData, appBufferSize); break; case BUFFER_UNDERFLOW: - peerNetData = enlargeBufferIfNeed(peerNetData, netBufferSize); break; case CLOSED: if (engine.isOutboundDone()) { @@ -143,7 +146,9 @@ public void handshake() throws IOException { netBuffer = enlargeBuffer(netBuffer, netBufferSize); break; case BUFFER_UNDERFLOW: - throw new SSLException("Buffer underflow occurred after a wrap."); + throw new SSLException( + "Should not happen, buffer underflow occurred after a wrap." + ); case CLOSED: try { writeOrTimeout(channel, netBuffer, endTime); @@ -171,7 +176,7 @@ public void handshake() throws IOException { break; } } - handshaked = true; + handshaked.set(true); } public byte[] read(ByteBuffer buffer) throws Exception { @@ -188,10 +193,16 @@ public byte[] read(ByteBuffer buffer) throws Exception { netBuffer = enlargeBuffer(netBuffer, appBufferSize); break; case BUFFER_UNDERFLOW: - tcpAppBuffer = enlargeBufferIfNeed(tcpAppBuffer, netBufferSize); break; case CLOSED: - safeCloseOutbound(); + try { + engine.closeOutbound(); + } catch (Throwable e) { + throw new EzyConnectionCloseException( + "ssl unwrap result status is CLOSE", + e + ); + } throw new EzyConnectionCloseException( "ssl unwrap result status is CLOSE" ); @@ -207,7 +218,7 @@ public byte[] read(ByteBuffer buffer) throws Exception { @Override public byte[] pack(byte[] bytes) throws Exception { - if (!handshaked) { + if (!handshaked.get()) { throw new SSLException("not handshaked"); } ByteBuffer buffer = ByteBuffer.wrap(bytes); @@ -222,9 +233,18 @@ public byte[] pack(byte[] bytes) throws Exception { netBuffer = enlargeBuffer(netBuffer, netBufferSize); break; case BUFFER_UNDERFLOW: - throw new IOException("Buffer underflow occurred after a wrap"); + throw new IOException( + "Should not happen, buffer underflow occurred after a wrap." + ); case CLOSED: - safeCloseOutbound(); + try { + engine.closeOutbound(); + } catch (Throwable e) { + throw new EzyConnectionCloseException( + "ssl wrap result status is CLOSE", + e + ); + } throw new EzyConnectionCloseException( "ssl wrap result status is CLOSE" ); @@ -238,7 +258,7 @@ public byte[] pack(byte[] bytes) throws Exception { return bytes; } - public static void writeOrTimeout( + public void writeOrTimeout( SocketChannel channel, ByteBuffer buffer, long timeoutAt @@ -247,20 +267,12 @@ public static void writeOrTimeout( while (buffer.hasRemaining()) { long currentTime = System.currentTimeMillis(); if (currentTime >= timeoutAt) { - throw new SSLException("Timeout"); + break; } channel.write(buffer); } } - private void safeCloseOutbound() { - try { - engine.closeOutbound(); - } catch (Throwable e) { - logger.info("close outbound error", e); - } - } - private ByteBuffer enlargeBuffer( ByteBuffer buffer, int sessionProposedCapacity @@ -269,21 +281,4 @@ private ByteBuffer enlargeBuffer( ? ByteBuffer.allocate(sessionProposedCapacity) : ByteBuffer.allocate(buffer.capacity() * 2); } - - private ByteBuffer enlargeBufferIfNeed( - ByteBuffer buffer, - int packageBufferSize - ) { - if (packageBufferSize < buffer.limit()) { - return buffer; - } else { - ByteBuffer replaceBuffer = enlargeBuffer( - buffer, - packageBufferSize - ); - buffer.flip(); - replaceBuffer.put(buffer); - return replaceBuffer; - } - } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java index e9be2113..d11bc2ce 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java @@ -60,11 +60,7 @@ private void processReadyKey(SelectionKey key) { key.interestOps(SelectionKey.OP_READ); } if (key.isReadable()) { - processReadableChannel((SocketChannel) key.channel()); + socketDataReceiver.tcpReceive((SocketChannel) key.channel()); } } - - protected void processReadableChannel(SocketChannel channel) { - socketDataReceiver.tcpReceive(channel); - } } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java index 58f05ad0..973ee9ec 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzySimpleNioHandlerGroupTest.java @@ -290,7 +290,7 @@ public void handleReceivedMessageHeaderIsNotRawBytes() throws Exception { // when MethodInvoker.create() .object(sut) - .method("handleReceivedMessage") + .method("fireMessageReceived") .param(EzyMessage.class, message) .call(); @@ -316,7 +316,7 @@ public void handleReceivedMessageStreamingNotEnable() throws Exception { // when MethodInvoker.create() .object(sut) - .method("handleReceivedMessage") + .method("fireMessageReceived") .param(EzyMessage.class, message) .call(); @@ -340,7 +340,7 @@ public void handleReceivedMessageSessionStreamingNotEnable() throws Exception { // when MethodInvoker.create() .object(sut) - .method("handleReceivedMessage") + .method("fireMessageReceived") .param(EzyMessage.class, message) .call(); diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java new file mode 100644 index 00000000..05608a29 --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java @@ -0,0 +1,74 @@ +package com.tvd12.ezyfoxserver.nio.testing.socket; + +import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketAcceptor; +import com.tvd12.ezyfoxserver.nio.socket.EzyNioSecureSocketChannel; +import com.tvd12.ezyfoxserver.socket.EzyChannel; +import com.tvd12.test.assertion.Asserts; +import com.tvd12.test.reflect.FieldUtil; +import com.tvd12.test.reflect.MethodInvoker; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; +import java.nio.channels.SocketChannel; + +import static org.mockito.Mockito.*; + +public class EzyNioSecureSocketAcceptorTest { + + private SSLEngine sslEngine; + private SSLContext sslContext; + private EzySSLContextSpiForTest sslContextSpi; + private SSLSession sslSession; + private SocketChannel socketChannel; + private EzyNioSecureSocketAcceptor instance; + private static final int sslHandshakeTimeout = 100; + + @BeforeMethod + public void setup() { + sslContext = mock(SSLContext.class); + sslContextSpi = mock(EzySSLContextSpiForTest.class); + sslEngine = mock(SSLEngine.class); + sslSession = mock(SSLSession.class); + socketChannel = mock(SocketChannel.class); + instance = new EzyNioSecureSocketAcceptor( + sslContext, + sslHandshakeTimeout + ); + FieldUtil.setFieldValue( + sslContext, + "contextSpi", + sslContextSpi + ); + when(sslContextSpi.engineCreateSSLEngine()).thenReturn(sslEngine); + when(sslEngine.getSession()).thenReturn(sslSession); + } + + @AfterMethod + public void verifyAll() throws Exception { + verifyNoMoreInteractions(sslContext); + verifyNoMoreInteractions(sslContextSpi); + verifyNoMoreInteractions(sslEngine); + verifyNoMoreInteractions(sslSession); + verify(socketChannel, times(1)).getLocalAddress(); + verify(socketChannel, times(1)).getRemoteAddress(); + verifyNoMoreInteractions(socketChannel); + } + + @Test + public void newChannelTest() { + // given + // when + EzyChannel channel = MethodInvoker.create() + .object(instance) + .method("newChannel") + .param(SocketChannel.class, socketChannel) + .invoke(EzyChannel.class); + + // then + Asserts.assertEqualsType(channel, EzyNioSecureSocketChannel.class); + } +} diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java index b8cec4f4..f79efd4f 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Matchers.any; @@ -38,7 +39,7 @@ public class EzyNioSecureSocketChannelTest { public void setup() { bytes = new byte[] {1, 2, 3}; buffer = ByteBuffer.allocate(bufferSize); - netBuffer = ByteBuffer.allocate(bufferSize); + netBuffer = ByteBuffer.allocate(bufferSize + 1); sslContext = mock(SSLContext.class); sslContextSpi = mock(EzySSLContextSpiForTest.class); sslEngine = mock(SSLEngine.class); @@ -63,9 +64,10 @@ public void setup() { public void beforeNotHandshakeMethod() { FieldUtil.setFieldValue(instance, "engine", sslEngine); FieldUtil.setFieldValue(instance, "netBuffer", netBuffer); - FieldUtil.setFieldValue(instance, "handshaked", true); FieldUtil.setFieldValue(instance, "appBufferSize", bufferSize); FieldUtil.setFieldValue(instance, "netBufferSize", bufferSize); + AtomicBoolean handshaked = FieldUtil.getFieldValue(instance, "handshaked"); + handshaked.set(true); } @AfterMethod @@ -114,6 +116,7 @@ public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsOk() throws Excep // then Asserts.assertTrue(instance.isHandshaked()); + Asserts.assertNotNull(instance.getPackingLock()); verifyAfterHandshake(); verify(socketChannel, times(1)) @@ -556,12 +559,6 @@ public void handleCaseHandshakeStatusIsNeedWrapEngineThrowException() throws Exc return SSLEngineResult.HandshakeStatus.FINISHED; }); - SSLEngineResult engineResult = new SSLEngineResult( - SSLEngineResult.Status.OK, - SSLEngineResult.HandshakeStatus.FINISHED, - 0, - 0 - ); SSLException error = new SSLException("test"); when( sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) @@ -757,6 +754,49 @@ public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteThrowExc .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); } + @Test + public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteTimeout() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + EzyThreads.sleep(sslHandshakeTimeout * 2); + return 0; + }); + + SSLEngineResult engineResult = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NEED_WRAP, + 0, + 0 + ); + when( + sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class)) + ).thenAnswer(it -> { + ByteBuffer buffer = it.getArgumentAt(1, ByteBuffer.class); + buffer.clear(); + buffer.put(new byte[] {1, 2, 3}); + return engineResult; + }); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.handshake() + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + + verifyAfterHandshake(); + verify(socketChannel, times(1)) + .write(any(ByteBuffer.class)); + + verify(sslEngine, times(1)).getHandshakeStatus(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + @Test public void handleCaseHandshakeStatusIsNeedTask() throws Exception { // given @@ -991,6 +1031,36 @@ public void packCaseBufferClosed() throws Exception { .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); } + @Test + public void packCaseBufferClosedButException() throws Exception { + // given + beforeNotHandshakeMethod(); + + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.wrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + RuntimeException exception = new RuntimeException("test"); + doThrow(exception).when(sslEngine).closeOutbound(); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.pack(bytes) + ); + + // then + Asserts.assertEqualsType(e, EzyConnectionCloseException.class); + + verify(sslEngine, times(1)).closeOutbound(); + verify(sslEngine, times(1)) + .wrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + @Test public void packNoRemaining() throws Exception { // when @@ -1002,6 +1072,21 @@ public void packNoRemaining() throws Exception { Asserts.assertEquals(actual, new byte[0]); } + @Test + public void packBeforeHandshake() { + // when + beforeNotHandshakeMethod(); + AtomicBoolean handshaked = FieldUtil.getFieldValue(instance, "handshaked"); + handshaked.set(false); + + Throwable e = Asserts.assertThrows(() -> + instance.pack(new byte[0]) + ); + + // then + Asserts.assertEqualsType(e, SSLException.class); + } + @Test public void readCaseOk() throws Exception { // given @@ -1079,6 +1164,52 @@ public void readCaseBufferOverFlow() throws Exception { .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); } + @Test + public void readCaseBufferOverFlowButNetBufferEnough() throws Exception { + // given + beforeNotHandshakeMethod(); + netBuffer = ByteBuffer.allocate(bufferSize / 2); + FieldUtil.setFieldValue(instance, "netBuffer", netBuffer); + + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger unwrapCallCount = new AtomicInteger(); + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(netBuffer); + return resultOk; + }); + + // when + byte[] actual = instance.read(buffer); + + // then + Asserts.assertEquals(actual, new byte[] {1, 2}); + + verify(sslEngine, times(2)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + @Test public void readCaseBufferUnderFlow() throws Exception { // given @@ -1127,6 +1258,9 @@ public void readCaseBufferUnderFlow() throws Exception { public void readCaseBufferClosed() throws Exception { // given beforeNotHandshakeMethod(); + netBuffer.put((byte) 1); + netBuffer.flip(); + netBuffer.get(); SSLEngineResult result = new SSLEngineResult( SSLEngineResult.Status.CLOSED, @@ -1151,6 +1285,39 @@ public void readCaseBufferClosed() throws Exception { .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); } + @Test + public void readCaseBufferClosedButException() throws Exception { + // given + beforeNotHandshakeMethod(); + netBuffer.put((byte) 1); + netBuffer.flip(); + netBuffer.get(); + + SSLEngineResult result = new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenReturn(result); + RuntimeException exception = new RuntimeException("test"); + doThrow(exception).when(sslEngine).closeOutbound(); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.read(buffer) + ); + + // then + Asserts.assertEqualsType(e, EzyConnectionCloseException.class); + + verify(sslEngine, times(1)).closeOutbound(); + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + @Test public void readNoRemaining() throws Exception { // given diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java index e680182d..a83f61d2 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSocketReaderTest.java @@ -11,10 +11,7 @@ import com.tvd12.ezyfoxserver.context.EzySimpleServerContext; import com.tvd12.ezyfoxserver.nio.builder.impl.EzyHandlerGroupBuilderFactoryImpl; import com.tvd12.ezyfoxserver.nio.factory.EzyHandlerGroupBuilderFactory; -import com.tvd12.ezyfoxserver.nio.socket.EzyNioSocketAcceptor; -import com.tvd12.ezyfoxserver.nio.socket.EzyNioSocketChannel; -import com.tvd12.ezyfoxserver.nio.socket.EzyNioSocketReader; -import com.tvd12.ezyfoxserver.nio.socket.EzySocketDataReceiver; +import com.tvd12.ezyfoxserver.nio.socket.*; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; import com.tvd12.ezyfoxserver.nio.wrapper.EzyNioSessionManager; import com.tvd12.ezyfoxserver.nio.wrapper.impl.EzyHandlerGroupManagerImpl; @@ -190,6 +187,53 @@ private EzyHandlerGroupManager newHandlerGroupManager() { .build(); } + @Test + public void processReadyKeyExceptionCase() throws Exception { + // given + EzyNioSocketReader instance = new EzyNioSocketReader(); + + EzyNioAcceptableConnectionsHandler acceptableConnectionsHandler = + mock(EzyNioAcceptableConnectionsHandler.class); + instance.setAcceptableConnectionsHandler(acceptableConnectionsHandler); + + Selector ownSelector = mock(Selector.class); + when(ownSelector.selectNow()).thenReturn(1); + instance.setOwnSelector(ownSelector); + + SelectionKey selectionKey = mock(SelectionKey.class); + when(selectionKey.isValid()).thenReturn(true); + when(selectionKey.readyOps()).thenReturn(SelectionKey.OP_READ); + when(ownSelector.selectedKeys()) + .thenReturn(Sets.newHashSet(selectionKey)); + + SocketChannel socketChannel = mock(SocketChannel.class); + when(selectionKey.channel()).thenReturn(socketChannel); + + EzySocketDataReceiver socketDataReceiver = mock(EzySocketDataReceiver.class); + RuntimeException exception = new RuntimeException("test"); + doThrow(exception).when(socketDataReceiver).tcpReceive(socketChannel); + instance.setSocketDataReceiver(socketDataReceiver); + + // when + instance.handleEvent(); + + // then + verify(acceptableConnectionsHandler, times(1)).handleAcceptableConnections(); + verifyNoMoreInteractions(acceptableConnectionsHandler); + + verify(ownSelector, times(1)).selectNow(); + verify(ownSelector, times(1)).selectedKeys(); + verifyNoMoreInteractions(ownSelector); + + verify(selectionKey, times(1)).isValid(); + verify(selectionKey, times(2)).readyOps(); + verify(selectionKey, times(1)).channel(); + verifyNoMoreInteractions(selectionKey); + + verify(socketDataReceiver, times(1)).tcpReceive(socketChannel); + verifyNoMoreInteractions(socketDataReceiver); + } + public static abstract class ExSocketChannel extends SocketChannel { public ExSocketChannel() { From 1b28b6aba17bae316cdba870c7057d597b8dca6d Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 11 Aug 2023 21:45:45 +0700 Subject: [PATCH 43/46] complete unit test and remove session locks --- .../api/EzySocketResponseApi.java | 4 +- .../controller/EzyHandshakeController.java | 4 +- .../entity/EzyAbstractSession.java | 12 ---- .../tvd12/ezyfoxserver/entity/EzySession.java | 9 --- .../entity/EzyAbstractSessionTest.java | 2 - .../socket/EzySecureSocketDataReceiver.java | 2 +- .../nio/socket/EzySocketDataReceiver.java | 14 ++--- .../EzyNioServerBootstrapBuilderImplTest.java | 61 +++++++++++++++++++ .../EzySecureSocketDataReceiverTest.java | 18 ++++++ .../socket/EzySocketDataReceiverTest.java | 47 ++++++++++++-- 10 files changed, 130 insertions(+), 43 deletions(-) diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java index 75a55aac..f986a6e2 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySocketResponseApi.java @@ -18,9 +18,7 @@ public EzySocketResponseApi(Object encoder) { } @Override - protected Object encodeData( - EzyArray data - ) throws Exception { + protected Object encodeData(EzyArray data) throws Exception { return encoder.encode(data); } diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java index aeeb51b5..d7fa61c4 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/controller/EzyHandshakeController.java @@ -39,12 +39,12 @@ protected void handleSocketSSL(EzyServerContext ctx, EzyHandshakeEvent event) { if (session.getConnectionType() == EzyConnectionType.WEBSOCKET) { return; } - boolean enableCustomizationSsl = ctx + boolean customizationSslEnable = ctx .getServer() .getSettings() .getSocket() .isCustomizationSslActive(); - if (!enableCustomizationSsl) { + if (!customizationSslEnable) { return; } if (!event.isEnableEncryption()) { diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java index d180fabb..de1bb712 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java @@ -2,7 +2,6 @@ import com.tvd12.ezyfox.constant.EzyConstant; import com.tvd12.ezyfox.entity.EzyEntity; -import com.tvd12.ezyfox.function.EzyFunctions; import com.tvd12.ezyfox.util.EzyProcessor; import com.tvd12.ezyfoxserver.delegate.EzySessionDelegate; import com.tvd12.ezyfoxserver.socket.*; @@ -13,9 +12,6 @@ import java.net.SocketAddress; import java.nio.channels.DatagramChannel; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; @Getter @Setter @@ -87,8 +83,6 @@ public abstract class EzyAbstractSession protected volatile boolean disconnectionRegistered; @Setter(AccessLevel.NONE) protected final Object disconnectionLock = new Object(); - @Setter(AccessLevel.NONE) - protected final Map locks = new ConcurrentHashMap<>(); public void setOwner(EzyUser owner) { this.ownerName = owner.getName(); @@ -137,11 +131,6 @@ public boolean isIdle() { return false; } - @Override - public Lock getLock(String name) { - return locks.computeIfAbsent(name, EzyFunctions.NEW_REENTRANT_LOCK_FUNC); - } - @Override public final void send(EzyPacket packet) { if (activated) { @@ -235,7 +224,6 @@ public void destroy() { this.readBytes = 0L; this.writtenBytes = 0L; this.connectionType = null; - this.locks.clear(); this.properties.clear(); this.droppedPackets = null; this.immediateDeliver = null; diff --git a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzySession.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzySession.java index d121abcf..39070b91 100644 --- a/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzySession.java +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzySession.java @@ -13,7 +13,6 @@ import java.io.Serializable; import java.net.SocketAddress; import java.nio.channels.DatagramChannel; -import java.util.concurrent.locks.Lock; @SuppressWarnings("MethodCount") public interface EzySession extends @@ -381,14 +380,6 @@ public interface EzySession extends */ EzyConstant getDisconnectReason(); - /** - * Get the lock of the session. - * - * @param name the lock name - * @return the lock - */ - Lock getLock(String name); - /** * Get client full ip address. * diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/entity/EzyAbstractSessionTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/entity/EzyAbstractSessionTest.java index 448f8e30..eee2660b 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/entity/EzyAbstractSessionTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/entity/EzyAbstractSessionTest.java @@ -95,8 +95,6 @@ public void test() { assert session.getDisconnectionQueue() == disconnectionQueue; assert !session.isDisconnectionRegistered(); assert session.getDisconnectionLock() != null; - assert session.getLocks().isEmpty(); - assert session.getLock("test") != null; assert !session.isActivated(); session.send(mock(EzyPacket.class)); diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index 423d1877..e2205774 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -34,8 +34,8 @@ protected void tcpReadBytes( return; } } + super.tcpReadBytes(channel, buffer); } - super.tcpReadBytes(channel, buffer); } @Override diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java index 97abcc92..d97dc864 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySocketDataReceiver.java @@ -15,7 +15,6 @@ import org.eclipse.jetty.websocket.api.Session; import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; import java.nio.channels.SocketChannel; import java.util.concurrent.ExecutorService; @@ -66,9 +65,6 @@ private void doTcpReceive(SocketChannel channel, ByteBuffer buffer) { try { tcpReadBytes(channel, buffer); } catch (Throwable e) { - if (e instanceof EzyConnectionCloseException) { - tcpCloseConnection(channel); - } logger.info( "I/O error at tcp-data-reader (channel: {})", channel, @@ -86,15 +82,17 @@ protected void tcpReadBytes( try { buffer.clear(); readBytes = channel.read(buffer); - } catch (ClosedChannelException e) { - // do nothing + if (readBytes > 0) { + processTcpReadBytes(channel, buffer); + } + } catch (EzyConnectionCloseException e) { + readBytes = -1; + exception = e; } catch (Throwable e) { exception = e; } if (readBytes == -1) { tcpCloseConnection(channel); - } else if (readBytes > 0) { - processTcpReadBytes(channel, buffer); } if (exception != null) { throw exception; diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java index 940ae245..7c4c4ea2 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/builder/EzyNioServerBootstrapBuilderImplTest.java @@ -3,7 +3,11 @@ import com.tvd12.ezyfox.codec.EzyCodecCreator; import com.tvd12.ezyfoxserver.EzyServer; import com.tvd12.ezyfoxserver.EzySimpleServer; +import com.tvd12.ezyfoxserver.api.EzyResponseApi; +import com.tvd12.ezyfoxserver.api.EzySecureProxyResponseApi; +import com.tvd12.ezyfoxserver.codec.EzyCodecFactory; import com.tvd12.ezyfoxserver.config.EzySimpleConfig; +import com.tvd12.ezyfoxserver.constant.EzyConnectionType; import com.tvd12.ezyfoxserver.nio.builder.impl.EzyNioServerBootstrapBuilderImpl; import com.tvd12.ezyfoxserver.nio.socket.EzySecureSocketDataReceiver; import com.tvd12.ezyfoxserver.nio.socket.EzySocketDataReceiver; @@ -86,6 +90,63 @@ public void newSocketDataReceiverBuilderSslTest() { verifyNoMoreInteractions(socketSetting); } + @Test + public void newResponseApiSslCase() { + // given + EzyNioServerBootstrapBuilderImpl instance = + new EzyNioServerBootstrapBuilderImpl(); + + EzyServer server = mock(EzySimpleServer.class); + EzySettings settings = mock(EzySettings.class); + when(server.getSettings()).thenReturn(settings); + + EzyLoggerSetting loggerSetting = mock(EzyLoggerSetting.class); + when(settings.getLogger()).thenReturn(loggerSetting); + + EzyLoggerSetting.EzyIgnoredCommandsSetting ignoredCommandsSetting = + mock(EzyLoggerSetting.EzyIgnoredCommandsSetting.class); + when(ignoredCommandsSetting.getCommands()).thenReturn(emptySet()); + when(loggerSetting.getIgnoredCommands()).thenReturn(ignoredCommandsSetting); + + EzySocketSetting socketSetting = mock(EzySocketSetting.class); + when(settings.getSocket()).thenReturn(socketSetting); + when(socketSetting.isCertificationSslActive()).thenReturn(true); + + instance.server(server); + + // when + EzyCodecFactory codecFactory = mock(EzyCodecFactory.class); + EzyResponseApi actual = MethodInvoker.create() + .object(instance) + .method("newResponseApi") + .param(EzyCodecFactory.class, codecFactory) + .invoke(EzyResponseApi.class); + + // then + Asserts.assertEqualsType(actual, EzySecureProxyResponseApi.class); + + verify(server, times(3)).getSettings(); + verifyNoMoreInteractions(server); + + verify(settings, times(1)).getSocket(); + verify(settings, times(1)).getLogger(); + verify(settings, times(1)).getZoneIds(); + verifyNoMoreInteractions(settings); + + verify(loggerSetting, times(1)).getIgnoredCommands(); + verifyNoMoreInteractions(loggerSetting); + + verify(ignoredCommandsSetting, times(1)).getCommands(); + verifyNoMoreInteractions(ignoredCommandsSetting); + + verify(socketSetting, times(1)).isCertificationSslActive(); + verifyNoMoreInteractions(socketSetting); + + verify(codecFactory, times(1)).newEncoder(EzyConnectionType.SOCKET); + verify(codecFactory, times(1)).newEncoder(EzyConnectionType.WEBSOCKET); + verifyNoMoreInteractions(codecFactory); + } + public static class ExCodecCreator implements EzyCodecCreator { @Override diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java index b0bc3048..35d4719d 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java @@ -135,6 +135,24 @@ public void tcpReadBytesAlreadyHandshakeCase() { verify(channel, times(1)).isHandshaked(); } + @Test + public void tcpReadBytesHandleGroupNullCase() throws Exception { + // given + SocketChannel socketChannel = mock(SocketChannel.class); + + // when + MethodInvoker.create() + .object(instance) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) + .param(ByteBuffer.class, buffer) + .invoke(); + + // then + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); + } + @Test public void readTcpBytesFromBufferTest() throws Exception { // given diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java index 6f7c72ad..a8252ee5 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySocketDataReceiverTest.java @@ -1,10 +1,12 @@ package com.tvd12.ezyfoxserver.nio.testing.socket; import com.tvd12.ezyfox.codec.EzyMessage; +import com.tvd12.ezyfoxserver.exception.EzyConnectionCloseException; import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; import com.tvd12.ezyfoxserver.nio.socket.EzySocketDataReceiver; import com.tvd12.ezyfoxserver.nio.websocket.EzyWsHandlerGroup; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; +import com.tvd12.test.assertion.Asserts; import com.tvd12.test.reflect.MethodInvoker; import com.tvd12.test.util.RandomUtil; import org.eclipse.jetty.websocket.api.Session; @@ -32,14 +34,18 @@ public void tcpReadBytesClosedChannelException() throws Exception { when(channel.read(buffer)).thenThrow(new ClosedChannelException()); // when - MethodInvoker.create() - .object(sut) - .method("tcpReadBytes") - .param(SocketChannel.class, channel) - .param(ByteBuffer.class, buffer) - .call(); + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(sut) + .method("tcpReadBytes") + .param(SocketChannel.class, channel) + .param(ByteBuffer.class, buffer) + .call() + ); // then + Asserts.assertEqualsType(e.getCause().getCause(), ClosedChannelException.class); + verify(handlerGroupManager, times(1)).getHandlerGroup(channel); } @@ -200,4 +206,33 @@ public void doWsReceive2ButException() throws Exception { verify(handlerGroupManager, times(1)).getHandlerGroup(session); verify(handlerGroup, times(1)).fireBytesReceived(payload, 0, payload.length); } + + @Test + public void tcpReadBytesEzyConnectionCloseException() throws Exception { + // given + EzyHandlerGroupManager handlerGroupManager = mock(EzyHandlerGroupManager.class); + EzySocketDataReceiver sut = EzySocketDataReceiver.builder() + .handlerGroupManager(handlerGroupManager) + .build(); + + ByteBuffer buffer = ByteBuffer.wrap(new byte[]{1, 2, 3}); + + SocketChannel channel = mock(SocketChannel.class); + when(channel.read(buffer)).thenThrow(new EzyConnectionCloseException("test")); + + // when + Throwable e = Asserts.assertThrows(() -> + MethodInvoker.create() + .object(sut) + .method("tcpReadBytes") + .param(SocketChannel.class, channel) + .param(ByteBuffer.class, buffer) + .call() + ); + + // then + Asserts.assertEqualsType(e.getCause().getCause(), EzyConnectionCloseException.class); + + verify(handlerGroupManager, times(1)).getHandlerGroup(channel); + } } From 27754129904f039f5f1ae1cf6ba65eadf6616f5f Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Fri, 11 Aug 2023 22:05:26 +0700 Subject: [PATCH 44/46] update settings --- ezyfox-server-core/src/main/resources/ezy-settings.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ezyfox-server-core/src/main/resources/ezy-settings.xml b/ezyfox-server-core/src/main/resources/ezy-settings.xml index 7104c6f5..4ac5ad53 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-settings.xml @@ -32,7 +32,7 @@ true 4096 8 - com.tvd12.ezyfoxserver.netty.codec.MsgPackCodecCreator + com.tvd12.ezyfox.codec.MsgPackCodecCreator @@ -56,8 +56,7 @@ ssl-config.properties com.tvd12.ezyfoxserver.ssl.EzySimpleSslConfigLoader - com.tvd12.ezyfoxserver.ssl.EzySimpleSslContextFactoryBuilder - + com.tvd12.ezyfoxserver.ssl.EzySimpleSslContextFactoryBuilder com.tvd12.ezyfox.codec.JacksonCodecCreator From cd007b47fdc4878232566244525bb827d8dd9f83 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sat, 12 Aug 2023 12:02:22 +0700 Subject: [PATCH 45/46] add max request size --- .../nio/EzySocketServerBootstrap.java | 3 +- .../socket/EzyNioSecureSocketAcceptor.java | 4 +- .../nio/socket/EzyNioSecureSocketChannel.java | 62 ++++++---- .../socket/EzySecureSocketDataReceiver.java | 2 +- .../testing/EzySocketServerBootstrapTest.java | 1 + .../EzyNioSecureSocketAcceptorTest.java | 6 +- .../socket/EzyNioSecureSocketChannelTest.java | 116 ++++++++++++++++-- 7 files changed, 160 insertions(+), 34 deletions(-) diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java index 7d422602..fa2dfe1d 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/EzySocketServerBootstrap.java @@ -91,7 +91,8 @@ private EzyNioSocketAcceptor newSocketAcceptor() { return socketSetting.isCertificationSslActive() ? new EzyNioSecureSocketAcceptor( sslContext, - socketSetting.getSslHandshakeTimeout() + socketSetting.getSslHandshakeTimeout(), + socketSetting.getMaxRequestSize() ) : new EzyNioSocketAcceptor(); } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java index d90202cb..9541c1e1 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java @@ -11,13 +11,15 @@ public class EzyNioSecureSocketAcceptor extends EzyNioSocketAcceptor { private final SSLContext sslContext; private final int sslHandshakeTimeout; + private final int maxRequestSize; @Override protected EzyChannel newChannel(SocketChannel clientChannel) { return new EzyNioSecureSocketChannel( clientChannel, sslContext, - sslHandshakeTimeout + sslHandshakeTimeout, + maxRequestSize ); } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java index 6ba310c7..b27ea7d6 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -12,6 +12,7 @@ import java.nio.channels.SocketChannel; import java.util.concurrent.atomic.AtomicBoolean; +import static com.tvd12.ezyfox.util.EzyProcessor.processWithLogException; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; @@ -25,19 +26,24 @@ public class EzyNioSecureSocketChannel private int netBufferSize; private final SSLContext sslContext; private final int sslHandshakeTimeout; + private final int sslMaxAppBufferSize; @Getter private final Object packingLock = new Object(); private final AtomicBoolean handshaked = new AtomicBoolean(); private final Logger logger = LoggerFactory.getLogger(getClass()); + private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); + public EzyNioSecureSocketChannel( SocketChannel channel, SSLContext sslContext, - int sslHandshakeTimeout + int sslHandshakeTimeout, + int maxRequestSize ) { super(channel); this.sslContext = sslContext; this.sslHandshakeTimeout = sslHandshakeTimeout; + this.sslMaxAppBufferSize = maxRequestSize * 2; } public boolean isHandshaked() { @@ -46,6 +52,18 @@ public boolean isHandshaked() { @SuppressWarnings("MethodLength") public void handshake() throws IOException { + if (!channel.isConnected()) { + logger.info("channel: {} closed", channel); + return; + } + if (engine != null) { + logger.info( + "channel: {} has already called handshake, handshaked: {}", + channel, + handshaked + ); + return; + } engine = sslContext.createSSLEngine(); engine.setUseClientMode(false); engine.beginHandshake(); @@ -53,8 +71,8 @@ public void handshake() throws IOException { SSLSession session = engine.getSession(); appBufferSize = session.getApplicationBufferSize(); netBufferSize = session.getPacketBufferSize(); + netBuffer = ByteBuffer.allocate(netBufferSize); - ByteBuffer appBuffer = ByteBuffer.allocate(appBufferSize); ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize); ByteBuffer peerNetData = ByteBuffer.allocate(netBufferSize); @@ -81,7 +99,7 @@ public void handshake() throws IOException { engine.closeInbound(); } catch (SSLException e) { logger.info( - "This engine was forced to close inbound, " + + "this engine was forced to close inbound, " + "without having received the proper SSL/TLS close " + "notification message from the peer, due to end of stream.", e @@ -98,7 +116,7 @@ public void handshake() throws IOException { handshakeStatus = result.getHandshakeStatus(); } catch (SSLException e) { logger.info( - "A problem was encountered while processing the data " + + "a problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + "Will try to properly close connection...", e @@ -109,8 +127,7 @@ public void handshake() throws IOException { } switch (result.getStatus()) { case BUFFER_OVERFLOW: - peerAppData = enlargeBuffer(peerAppData, appBufferSize); - break; + throw new EzyConnectionCloseException("max request size"); case BUFFER_UNDERFLOW: break; case CLOSED: @@ -128,13 +145,13 @@ public void handshake() throws IOException { case NEED_WRAP: netBuffer.clear(); try { - result = engine.wrap(appBuffer, netBuffer); + result = engine.wrap(EMPTY_BUFFER, netBuffer); handshakeStatus = result.getHandshakeStatus(); } catch (SSLException e) { engine.closeOutbound(); handshakeStatus = engine.getHandshakeStatus(); logger.info( - "A problem was encountered while processing the data " + + "a problem was encountered while processing the data " + "that caused the SSLEngine to abort. " + "Will try to properly close connection...", e @@ -143,11 +160,11 @@ public void handshake() throws IOException { } switch (result.getStatus()) { case BUFFER_OVERFLOW: - netBuffer = enlargeBuffer(netBuffer, netBufferSize); + netBuffer = ByteBuffer.allocate(netBuffer.capacity() * 2); break; case BUFFER_UNDERFLOW: throw new SSLException( - "Should not happen, buffer underflow occurred after a wrap." + "should not happen, buffer underflow occurred after a wrap." ); case CLOSED: try { @@ -155,7 +172,7 @@ public void handshake() throws IOException { peerNetData.clear(); } catch (Exception e) { logger.info( - "Failed to send server's close message " + + "failed to send server's close message " + "due to socket channel's failure.", e ); @@ -190,7 +207,11 @@ public byte[] read(ByteBuffer buffer) throws Exception { SSLEngineResult result = engine.unwrap(netBuffer, tcpAppBuffer); switch (result.getStatus()) { case BUFFER_OVERFLOW: - netBuffer = enlargeBuffer(netBuffer, appBufferSize); + int doubleSize = tcpAppBuffer.capacity() * 2; + if (doubleSize > sslMaxAppBufferSize) { + throw new EzyConnectionCloseException("max request size"); + } + tcpAppBuffer = ByteBuffer.allocate(doubleSize); break; case BUFFER_UNDERFLOW: break; @@ -230,11 +251,11 @@ public byte[] pack(byte[] bytes) throws Exception { ); switch (result.getStatus()) { case BUFFER_OVERFLOW: - netBuffer = enlargeBuffer(netBuffer, netBufferSize); + netBuffer = ByteBuffer.allocate(netBuffer.capacity() * 2); break; case BUFFER_UNDERFLOW: throw new IOException( - "Should not happen, buffer underflow occurred after a wrap." + "should not happen, buffer underflow occurred after a wrap." ); case CLOSED: try { @@ -273,12 +294,11 @@ public void writeOrTimeout( } } - private ByteBuffer enlargeBuffer( - ByteBuffer buffer, - int sessionProposedCapacity - ) { - return sessionProposedCapacity > buffer.capacity() - ? ByteBuffer.allocate(sessionProposedCapacity) - : ByteBuffer.allocate(buffer.capacity() * 2); + @Override + public void close() { + super.close(); + if (engine != null) { + processWithLogException(engine::closeOutbound); + } } } diff --git a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java index e2205774..24068f05 100644 --- a/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -26,7 +26,7 @@ protected void tcpReadBytes( if (!secureChannel.isHandshaked()) { try { secureChannel.handshake(); - } catch (Exception e) { + } catch (Throwable e) { logger.info("handshake failed on channel: {}", channel, e); handlerGroup.enqueueDisconnection( EzyDisconnectReason.SSH_HANDSHAKE_FAILED diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java index 40f3b839..e1593580 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java @@ -57,6 +57,7 @@ public void verifyAll() { verify(socketSetting, times(1)).isCertificationSslActive(); verify(socketSetting, times(1)).getSslHandshakeTimeout(); + verify(socketSetting, times(1)).getMaxRequestSize(); verifyNoMoreInteractions(socketSetting); verifyNoMoreInteractions(sslContext); } diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java index 05608a29..4e50d2e7 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java @@ -25,7 +25,8 @@ public class EzyNioSecureSocketAcceptorTest { private SSLSession sslSession; private SocketChannel socketChannel; private EzyNioSecureSocketAcceptor instance; - private static final int sslHandshakeTimeout = 100; + private static final int SSL_HANDSHAKE_TIMEOUT = 100; + private static final int MAX_REQUEST_SIZE = 1024; @BeforeMethod public void setup() { @@ -36,7 +37,8 @@ public void setup() { socketChannel = mock(SocketChannel.class); instance = new EzyNioSecureSocketAcceptor( sslContext, - sslHandshakeTimeout + SSL_HANDSHAKE_TIMEOUT, + MAX_REQUEST_SIZE ); FieldUtil.setFieldValue( sslContext, diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java index f79efd4f..27425694 100644 --- a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java @@ -33,7 +33,8 @@ public class EzyNioSecureSocketChannelTest { private SocketChannel socketChannel; private EzyNioSecureSocketChannel instance; private final int bufferSize = 128; - private static final int sslHandshakeTimeout = 100; + private static final int SSL_HANDSHAKE_TIMEOUT = 100; + private static final int MAX_REQUEST_SIZE = 128; @BeforeMethod public void setup() { @@ -48,7 +49,8 @@ public void setup() { instance = new EzyNioSecureSocketChannel( socketChannel, sslContext, - sslHandshakeTimeout + SSL_HANDSHAKE_TIMEOUT, + MAX_REQUEST_SIZE ); FieldUtil.setFieldValue( sslContext, @@ -59,6 +61,7 @@ public void setup() { when(sslEngine.getSession()).thenReturn(sslSession); when(sslSession.getPacketBufferSize()).thenReturn(bufferSize); when(sslSession.getApplicationBufferSize()).thenReturn(bufferSize); + when(socketChannel.isConnected()).thenReturn(true); } public void beforeNotHandshakeMethod() { @@ -90,6 +93,8 @@ private void verifyAfterHandshake() throws Exception { verify(sslSession, times(1)).getApplicationBufferSize(); verify(sslSession, times(1)).getPacketBufferSize(); + + verify(socketChannel, times(1)).isConnected(); } @Test @@ -327,17 +332,17 @@ public void handleCaseHandshakeStatusIsNeedUnwrapEngineStatusIsBufferOverflow() }); // when - instance.handshake(); + Throwable e = Asserts.assertThrows(() -> instance.handshake()); // then - Asserts.assertTrue(instance.isHandshaked()); + Asserts.assertEqualsType(e, EzyConnectionCloseException.class); verifyAfterHandshake(); - verify(socketChannel, times(2)) + verify(socketChannel, times(1)) .read(any(ByteBuffer.class)); verify(sslEngine, times(1)).getHandshakeStatus(); - verify(sslEngine, times(2)) + verify(sslEngine, times(1)) .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); } @@ -761,7 +766,7 @@ public void handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteTimeout( SSLEngineResult.HandshakeStatus.NEED_WRAP ); when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { - EzyThreads.sleep(sslHandshakeTimeout * 2); + EzyThreads.sleep(SSL_HANDSHAKE_TIMEOUT * 2); return 0; }); @@ -874,7 +879,7 @@ public void handleCaseHandshakeStatusIsNeedUnwrapTimeout() throws Exception { ); int readBytes = RandomUtil.randomSmallInt(); when(socketChannel.read(any(ByteBuffer.class))).thenAnswer(it -> { - EzyThreads.sleep(sslHandshakeTimeout * 2); + EzyThreads.sleep(SSL_HANDSHAKE_TIMEOUT * 2); return readBytes; }); @@ -905,6 +910,34 @@ public void handleCaseHandshakeStatusIsNeedUnwrapTimeout() throws Exception { .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); } + @Test + public void handleCaseHandshakeChannelNotConnected() throws Exception { + // given + when(socketChannel.isConnected()).thenReturn(false); + + // whe + instance.handshake(); + + // then + Asserts.assertFalse(instance.isHandshaked()); + + verify(socketChannel, times(1)).isConnected(); + } + + @Test + public void handleCaseHandshakeEngineNotNull() throws Exception { + // given + FieldUtil.setFieldValue(instance, "engine", sslEngine); + + // whe + instance.handshake(); + + // then + Asserts.assertFalse(instance.isHandshaked()); + + verify(socketChannel, times(1)).isConnected(); + } + @Test public void packCaseOk() throws Exception { // given @@ -1210,6 +1243,53 @@ public void readCaseBufferOverFlowButNetBufferEnough() throws Exception { .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); } + @Test + public void readCaseBufferOverFlowButMaxSize() throws Exception { + // given + beforeNotHandshakeMethod(); + FieldUtil.setFieldValue(instance, "sslMaxAppBufferSize", bufferSize/2); + + buffer.clear(); + buffer.put(new byte[] {1, 2}); + buffer.flip(); + SSLEngineResult resultBufferOverflow = new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + SSLEngineResult resultOk = new SSLEngineResult( + SSLEngineResult.Status.OK, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, + 0 + ); + + AtomicInteger unwrapCallCount = new AtomicInteger(); + when(sslEngine.unwrap(any(ByteBuffer.class), any(ByteBuffer.class))) + .thenAnswer(it -> { + int callCount = unwrapCallCount.incrementAndGet(); + if (callCount == 1) { + return resultBufferOverflow; + } + ByteBuffer tcpNetBuffer = it.getArgumentAt(1, ByteBuffer.class); + tcpNetBuffer.clear(); + tcpNetBuffer.put(netBuffer); + return resultOk; + }); + + // when + Throwable e = Asserts.assertThrows(() -> + instance.read(buffer) + ); + + // then + Asserts.assertEqualsType(e, EzyConnectionCloseException.class); + + verify(sslEngine, times(1)) + .unwrap(any(ByteBuffer.class), any(ByteBuffer.class)); + } + @Test public void readCaseBufferUnderFlow() throws Exception { // given @@ -1332,4 +1412,24 @@ public void readNoRemaining() throws Exception { // then Asserts.assertEquals(actual, new byte[0]); } + + @Test + public void closeNormalCase() { + // given + FieldUtil.setFieldValue(instance, "engine", sslEngine); + + // when + instance.close(); + + // then + verify(sslEngine, times(1)).closeOutbound(); + } + + @Test + public void closeBeforeHandshakeCase() { + // given + // when + // then + instance.close(); + } } From 80ec6ec9e4b00efecf1ee9cd51b19477d3850704 Mon Sep 17 00:00:00 2001 From: Dung Ta Van Date: Sun, 15 Oct 2023 14:20:04 +0700 Subject: [PATCH 46/46] resolve review comments --- .../tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java index 0e132b5b..8194ae79 100644 --- a/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/socket/EzySimplePacketTest.java @@ -29,7 +29,6 @@ public void replaceDataTest() { EzySimplePacket packet = new EzySimplePacket(); packet.setData("hello"); - // when packet.replaceData("world");