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/api/EzyAbstractResponseApi.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzyAbstractResponseApi.java index f6baa35b..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 @@ -2,13 +2,18 @@ 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; -public abstract class EzyAbstractResponseApi implements EzyResponseApi { +public abstract class EzyAbstractResponseApi + extends EzyLoggable + implements EzyResponseApi { @Override public void response( @@ -32,13 +37,40 @@ protected final void normalResponse( return; } Object bytes = encodeData(pack.getData()); + EzyConstant transportType = pack.getTransportType(); if (immediate) { for (EzySession session : recipients) { - session.sendNow(createPacket(bytes, pack)); + EzyPacket packet = null; + try { + 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) { - session.send(createPacket(bytes, pack)); + EzyPacket packet = null; + try { + 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); + } } } } @@ -53,26 +85,106 @@ protected final void secureResponse( return; } byte[] messageContent = dataToMessageContent(pack.getData()); + EzyConstant transportType = pack.getTransportType(); if (immediate) { for (EzySession session : recipients) { - byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - session.sendNow(createPacket(bytes, pack)); + EzyPacket packet = null; + try { + byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); + 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) { - byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); - session.send(createPacket(bytes, pack)); + EzyPacket packet = null; + try { + byte[] bytes = encryptMessageContent(messageContent, session.getSessionKey()); + 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); + } } } } - protected EzySimplePacket createPacket(Object bytes, EzyPackage pack) { + private EzyPacket createPacket( + EzySession session, + EzyConstant transportType, + Object bytes + ) { + EzyConstant actualTransportType = transportType; + if (actualTransportType == EzyTransportType.UDP_OR_TCP) { + actualTransportType = session.getDatagramChannelPool() != null + ? EzyTransportType.UDP + : EzyTransportType.TCP; + } + return createPacket(actualTransportType, bytes); + } + + protected EzySimplePacket createPacket( + EzyConstant transportType, + Object bytes + ) { EzySimplePacket packet = new EzySimplePacket(); - packet.setTransportType(pack.getTransportType()); + packet.setTransportType(transportType); packet.setData(bytes); 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; 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..537d7020 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/api/EzySecureSocketResponseApi.java @@ -0,0 +1,64 @@ +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; +import com.tvd12.ezyfoxserver.socket.EzySecureChannel; + +import java.io.IOException; + +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) { + throw new IOException("session destroyed"); + } + EzySecureChannel secureChannel = (EzySecureChannel) channel; + try { + synchronized (secureChannel.getPackingLock()) { + byte[] packedBytes = secureChannel.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) { + throw new IOException("session destroyed"); + } + EzySecureChannel secureChannel = (EzySecureChannel) channel; + try { + synchronized (secureChannel.getPackingLock()) { + byte[] packedBytes = secureChannel.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 400ddad4..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 @@ -12,7 +12,9 @@ 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 @@ -21,14 +23,21 @@ protected Object encodeData(EzyArray data) throws Exception { } @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 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/builder/EzyAbstractServerBootstrapBuilder.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/builder/EzyAbstractServerBootstrapBuilder.java index 5038e14f..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 @@ -45,7 +45,11 @@ protected EzyServerContext newServerContext(EzyServer server) { } protected SSLContext newSslContext(EzySslConfigSetting sslConfig) { - if (getWebsocketSetting().isSslActive()) { + EzySocketSetting socketSetting = getSocketSetting(); + EzyWebSocketSetting webSocketSetting = getWebsocketSetting(); + boolean activeSslForSocket = socketSetting.isCertificationSslActive(); + 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/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/constant/SslType.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/constant/SslType.java new file mode 100644 index 00000000..92882baf --- /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 { + 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 b633d5c5..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,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 customizationSslEnable = ctx + .getServer() + .getSettings() + .getSocket() + .isCustomizationSslActive(); + if (!customizationSslEnable) { return; } if (!event.isEnableEncryption()) { @@ -56,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/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/entity/EzyAbstractSession.java index 4d57562f..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 @@ -86,9 +82,7 @@ public abstract class EzyAbstractSession @Setter(AccessLevel.NONE) protected volatile boolean disconnectionRegistered; @Setter(AccessLevel.NONE) - protected Object disconnectionLock = new Object(); - @Setter(AccessLevel.NONE) - protected Map locks = new ConcurrentHashMap<>(); + protected final Object disconnectionLock = new Object(); 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) { @@ -180,7 +169,6 @@ private void addPacketToSessionQueue(EzyPacket packet) { } } - @SuppressWarnings("SynchronizeOnNonFinalField") @Override public void disconnect(EzyConstant disconnectReason) { synchronized (disconnectionLock) { @@ -236,12 +224,7 @@ public void destroy() { this.readBytes = 0L; this.writtenBytes = 0L; this.connectionType = null; - this.disconnectionLock = null; - if (locks != null) { - 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/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/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..b16a1866 --- /dev/null +++ b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/exception/EzyConnectionCloseException.java @@ -0,0 +1,14 @@ +package com.tvd12.ezyfoxserver.exception; + +import java.io.IOException; + +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/setting/EzySimpleSocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySimpleSocketSetting.java index 709b91a7..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 @@ -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,12 @@ public class EzySimpleSocketSetting extends EzyAbstractSocketSetting implements EzySocketSetting { + @XmlElement(name = "ssl-type") + protected SslType sslType; + + @XmlElement(name = "ssl-handshake-timeout") + protected int sslHandshakeTimeout; + @XmlElement(name = "max-request-size") protected int maxRequestSize; @@ -31,6 +38,8 @@ public class EzySimpleSocketSetting public EzySimpleSocketSetting() { super(); setPort(3005); + setSslType(SslType.CUSTOMIZATION); + setSslHandshakeTimeout(350); setMaxRequestSize(4096); setWriterThreadPoolSize(8); setCodecCreator("com.tvd12.ezyfox.codec.MsgPackCodecCreator"); @@ -40,6 +49,8 @@ public EzySimpleSocketSetting() { public Map 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); 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/EzySocketSetting.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/setting/EzySocketSetting.java index 0666bdcd..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 @@ -1,10 +1,23 @@ package com.tvd12.ezyfoxserver.setting; +import com.tvd12.ezyfoxserver.constant.SslType; + public interface EzySocketSetting extends EzyBaseSocketSetting { + SslType getSslType(); + + int getSslHandshakeTimeout(); boolean isTcpNoDelay(); int getMaxRequestSize(); int getWriterThreadPoolSize(); + + default boolean isCertificationSslActive() { + return isSslActive() && getSslType() == SslType.CERTIFICATION; + } + + 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 17a0b915..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 @@ -1,21 +1,37 @@ package com.tvd12.ezyfoxserver.setting; +import com.tvd12.ezyfoxserver.constant.SslType; + public class EzySocketSettingBuilder extends EzyAbstractSocketSettingBuilder< EzySimpleSocketSetting, EzySocketSettingBuilder> { + protected SslType sslType; + protected int sslHandshakeTimeout; protected int maxRequestSize; protected boolean tcpNoDelay; protected int writerThreadPoolSize; public EzySocketSettingBuilder() { this.port = 3005; + this.sslType = SslType.CUSTOMIZATION; + this.sslHandshakeTimeout = 350; 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 sslHandshakeTimeout(int sslHandshakeTimeout) { + this.sslHandshakeTimeout = sslHandshakeTimeout; + return this; + } + public EzySocketSettingBuilder maxRequestSize(int maxRequestSize) { this.maxRequestSize = maxRequestSize; return this; @@ -36,6 +52,8 @@ protected EzySimpleSocketSetting newSetting() { EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); setting.setTcpNoDelay(tcpNoDelay); setting.setSslActive(sslActive); + setting.setSslType(sslType); + setting.setSslHandshakeTimeout(sslHandshakeTimeout); setting.setMaxRequestSize(maxRequestSize); setting.setWriterThreadPoolSize(writerThreadPoolSize); setting.setCodecCreator(codecCreator); 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/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/EzySecureChannel.java b/ezyfox-server-core/src/main/java/com/tvd12/ezyfoxserver/socket/EzySecureChannel.java new file mode 100644 index 00000000..1cb0cfde --- /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 getPackingLock(); +} 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..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; @@ -25,10 +30,14 @@ public void setFragment(Object fragment) { @Override public int getSize() { - if (data instanceof String) { - return ((String) data).length(); + Object currentData = data; + if (currentData == null) { + return 0; + } + if (currentData instanceof String) { + return ((String) currentData).length(); } - return ((byte[]) data).length; + return ((byte[]) currentData).length; } @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 938a2c82..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,27 +16,26 @@ 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); } catch (InterruptedException e) { logger.info("socket-writer thread interrupted"); - } catch (Throwable throwable) { - logger.warn("problems in socket-writer, thread", throwable); + } catch (Throwable e) { + logger.info("problems in socket-writer, thread", e); } } - 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/resources/ezy-settings-1.0.0.xsd b/ezyfox-server-core/src/main/resources/ezy-settings-1.0.0.xsd index acd6c327..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 @@ -63,6 +63,8 @@ + + @@ -75,7 +77,6 @@ - @@ -97,6 +98,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..4ac5ad53 100644 --- a/ezyfox-server-core/src/main/resources/ezy-settings.xml +++ b/ezyfox-server-core/src/main/resources/ezy-settings.xml @@ -27,10 +27,12 @@
0.0.0.0
true true + CUSTOMIZATION + 300 true 4096 8 - com.tvd12.ezyfoxserver.netty.codec.MsgPackCodecCreator + com.tvd12.ezyfox.codec.MsgPackCodecCreator @@ -54,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 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-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..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 @@ -5,7 +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; @@ -73,6 +78,365 @@ 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 + 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 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 + 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 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 + 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 secureResponseImmediateSendDueToCreatePacketException() throws Exception { + // given + 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 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(); + + 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, 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(); + verifyNoMoreInteractions(session); + } + private static class InternalResponseApi extends EzyAbstractResponseApi { @Override @@ -85,4 +449,43 @@ 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) { + return new byte[0]; + } + + @Override + 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/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 new file mode 100644 index 00000000..e8e4b9aa --- /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.getPackingLock()).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)).getPackingLock(); + + 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)).getPackingLock(); + + 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)).getPackingLock(); + + 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)).getPackingLock(); + + 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/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/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/controller/EzyHandShakeControllerTest.java b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/controller/EzyHandShakeControllerTest.java index 13c2ce63..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 @@ -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.isCustomizationSslActive()).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)).isCustomizationSslActive(); verify(session, times(1)).setClientId(clientId); verify(session, times(1)).setClientKey(clientKey); verify(session, times(1)).setClientType(clientType); @@ -142,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); @@ -155,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 @@ -173,7 +218,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); @@ -218,7 +263,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); @@ -276,7 +321,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); 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-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-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-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..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 @@ -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.CERTIFICATION); + + // when + // then + Asserts.assertTrue(setting.isCertificationSslActive()); + } + + @Test + public void isL4SslActiveFalseDueToSslEnableTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(false); + setting.setSslType(SslType.CERTIFICATION); + + // when + // then + Asserts.assertFalse(setting.isCertificationSslActive()); + } + + @Test + public void isL4SslActiveFalseDueToSslTypeTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.CUSTOMIZATION); + + // when + // then + Asserts.assertFalse(setting.isCertificationSslActive()); + } + + @Test + public void isL7SslActiveTrueTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.CUSTOMIZATION); + + // when + // then + Asserts.assertTrue(setting.isCustomizationSslActive()); + } + + @Test + public void isL7SslActiveFalseDueToSslEnableTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(false); + setting.setSslType(SslType.CUSTOMIZATION); + + // when + // then + Asserts.assertFalse(setting.isCustomizationSslActive()); + } + + @Test + public void isL7SslActiveFalseDueToSslTypeTest() { + // given + EzySimpleSocketSetting setting = new EzySimpleSocketSetting(); + setting.setSslActive(true); + setting.setSslType(SslType.CERTIFICATION); + + // when + // then + Asserts.assertFalse(setting.isCustomizationSslActive()); + } } 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..3c8bccbc --- /dev/null +++ b/ezyfox-server-core/src/test/java/com/tvd12/ezyfoxserver/testing/setting/EzySocketSettingBuilderTest.java @@ -0,0 +1,33 @@ +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(); + + // when + EzySocketSetting setting = new EzySocketSettingBuilder() + .sslType(sslType) + .sslHandshakeTimeout(sslHandshakeTimeout) + .build(); + + // then + Asserts.assertEquals(setting.getSslType(), sslType); + Asserts.assertEquals( + setting.getSslHandshakeTimeout(), + sslHandshakeTimeout + ); + } +} 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..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 @@ -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 { @@ -9,6 +10,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); @@ -20,4 +22,17 @@ 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-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/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/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 a16985a4..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 @@ -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; @@ -22,12 +23,15 @@ public class EzySocketServerBootstrap extends EzyAbstractSocketServerBootstrap { private Selector acceptSelector; private ServerSocket serverSocket; 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(); } public static Builder builder() { @@ -64,12 +68,17 @@ 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); } private void startSocketHandlers() throws Exception { - EzyNioSocketAcceptor socketAcceptor = new EzyNioSocketAcceptor(); + EzyNioSocketAcceptor socketAcceptor = newSocketAcceptor(); writingLoopHandler = newWritingLoopHandler(); readingLoopHandler = newReadingLoopHandler(socketAcceptor); socketAcceptanceLoopHandler = newSocketAcceptanceLoopHandler(socketAcceptor); @@ -78,9 +87,19 @@ private void startSocketHandlers() throws Exception { writingLoopHandler.start(); } + private EzyNioSocketAcceptor newSocketAcceptor() { + return socketSetting.isCertificationSslActive() + ? new EzyNioSecureSocketAcceptor( + sslContext, + socketSetting.getSslHandshakeTimeout(), + socketSetting.getMaxRequestSize() + ) + : new EzyNioSocketAcceptor(); + } + private EzySocketEventLoopHandler newWritingLoopHandler() { EzySocketWritingLoopHandler loopHandler = new EzySocketWritingLoopHandler(); - loopHandler.setThreadPoolSize(getSocketWriterPoolSize()); + loopHandler.setThreadPoolSize(socketSetting.getWriterThreadPoolSize()); loopHandler.setEventHandlerSupplier(() -> { EzySocketWriter eventHandler = new EzyNioSocketWriter(); eventHandler.setWriterGroupFetcher(handlerGroupManager); @@ -91,9 +110,10 @@ private EzySocketEventLoopHandler newWritingLoopHandler() { } private EzySocketEventLoopHandler newReadingLoopHandler( - EzyNioAcceptableConnectionsHandler acceptableConnectionsHandler) { + EzyNioAcceptableConnectionsHandler acceptableConnectionsHandler + ) { EzySocketEventLoopOneHandler loopHandler = new EzyNioSocketReadingLoopHandler(); - loopHandler.setThreadPoolSize(getSocketReaderPoolSize()); + loopHandler.setThreadPoolSize(getSocketReaderThreadPoolSize()); EzyNioSocketReader eventHandler = new EzyNioSocketReader(); eventHandler.setOwnSelector(readSelector); eventHandler.setSocketDataReceiver(socketDataReceiver); @@ -105,8 +125,8 @@ private EzySocketEventLoopHandler newReadingLoopHandler( private EzySocketEventLoopHandler newSocketAcceptanceLoopHandler( EzyNioSocketAcceptor socketAcceptor) { EzySocketEventLoopOneHandler loopHandler = new EzyNioSocketAcceptanceLoopHandler(); - loopHandler.setThreadPoolSize(getSocketAcceptorPoolSize()); - socketAcceptor.setTcpNoDelay(getSocketTcpNoDelay()); + loopHandler.setThreadPoolSize(getSocketAcceptorThreadPoolSize()); + socketAcceptor.setTcpNoDelay(socketSetting.isTcpNoDelay()); socketAcceptor.setReadSelector(readSelector); socketAcceptor.setOwnSelector(acceptSelector); socketAcceptor.setHandlerGroupManager(handlerGroupManager); @@ -122,36 +142,25 @@ private ServerSocketChannel newServerSocketChannel() throws Exception { return ServerSocketChannel.open(); } - private int getSocketReaderPoolSize() { + private int getSocketReaderThreadPoolSize() { return EzyNioThreadPoolSizes.SOCKET_READER; } - private int getSocketWriterPoolSize() { - return getSocketSetting().getWriterThreadPoolSize(); - } - - private int getSocketAcceptorPoolSize() { + 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(); - } + public static class Builder extends EzyAbstractSocketServerBootstrap.Builder< + Builder, + EzySocketServerBootstrap + > { - private EzySocketSetting getSocketSetting() { - return getServerSettings().getSocket(); - } + private SSLContext sslContext; - public static class Builder - extends EzyAbstractSocketServerBootstrap.Builder { + public Builder sslContext(SSLContext sslContext) { + this.sslContext = sslContext; + return this; + } @Override public EzySocketServerBootstrap build() { 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/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/builder/impl/EzyNioServerBootstrapBuilderImpl.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/builder/impl/EzyNioServerBootstrapBuilderImpl.java index f62a22c6..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 @@ -2,19 +2,18 @@ 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; 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; @@ -41,10 +40,14 @@ protected EzyServerBootstrap newServerBootstrap() { socketDisconnectionQueue, socketSessionTicketsQueue, websocketSessionTicketsQueue, - sessionTicketsRequestQueues); + sessionTicketsRequestQueues + ); EzyHandlerGroupManager handlerGroupManager = newHandlerGroupManager( - handlerGroupBuilderFactory); - EzySocketDataReceiver socketDataReceiver = newSocketDataReceiver(handlerGroupManager); + handlerGroupBuilderFactory + ); + EzySocketDataReceiver socketDataReceiver = newSocketDataReceiver( + handlerGroupManager + ); EzyNioServerBootstrap bootstrap = new EzyNioServerBootstrap(); bootstrap.setResponseApi(responseApi); bootstrap.setStreamingApi(streamingApi); @@ -94,7 +97,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() { @@ -102,13 +108,22 @@ private ExecutorService newStatsThreadPool() { return EzyExecutors.newFixedThreadPool(threadPoolSize, "statistics"); } - private EzySocketDataReceiver newSocketDataReceiver(EzyHandlerGroupManager handlerGroupManager) { - return 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/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 eefb3834..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 @@ -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; @@ -169,14 +171,8 @@ 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 - ? EzyTransportType.UDP - : EzyTransportType.TCP; - } int writeBytes = transportType == EzyTransportType.TCP ? writePacketToSocket(packet, writeBuffer) : writeUdpPacketToSocket(packet, writeBuffer); @@ -186,12 +182,11 @@ protected final void executeSendingPacket(EzyPacket packet, Object writeBuffer) int packetSize = packet.getSize(); networkStats.addWriteErrorPackets(1); networkStats.addWriteErrorBytes(packetSize); - logger.warn( - "can't send {} bytes to session: {}, error: {}({})", + logger.info( + "can't send {} bytes to session: {}", packetSize, session, - e.getClass().getName(), - e.getMessage() + e ); } } @@ -238,13 +233,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/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/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/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/EzyNioSecureSocketAcceptor.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java new file mode 100644 index 00000000..9541c1e1 --- /dev/null +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketAcceptor.java @@ -0,0 +1,25 @@ +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; + private final int maxRequestSize; + + @Override + protected EzyChannel newChannel(SocketChannel clientChannel) { + return new EzyNioSecureSocketChannel( + clientChannel, + sslContext, + 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 new file mode 100644 index 00000000..b27ea7d6 --- /dev/null +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSecureSocketChannel.java @@ -0,0 +1,304 @@ +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.*; +import java.io.IOException; +import java.nio.ByteBuffer; +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; + +public class EzyNioSecureSocketChannel + extends EzyNioSocketChannel + implements EzySecureChannel { + + private SSLEngine engine; + private ByteBuffer netBuffer; + private int appBufferSize; + 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 maxRequestSize + ) { + super(channel); + this.sslContext = sslContext; + this.sslHandshakeTimeout = sslHandshakeTimeout; + this.sslMaxAppBufferSize = maxRequestSize * 2; + } + + public boolean isHandshaked() { + return handshaked.get(); + } + + @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(); + + SSLSession session = engine.getSession(); + appBufferSize = session.getApplicationBufferSize(); + netBufferSize = session.getPacketBufferSize(); + + netBuffer = ByteBuffer.allocate(netBufferSize); + 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: + throw new EzyConnectionCloseException("max request size"); + case BUFFER_UNDERFLOW: + 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(EMPTY_BUFFER, 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 = ByteBuffer.allocate(netBuffer.capacity() * 2); + break; + case BUFFER_UNDERFLOW: + throw new SSLException( + "should not happen, 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.set(true); + } + + 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: + int doubleSize = tcpAppBuffer.capacity() * 2; + if (doubleSize > sslMaxAppBufferSize) { + throw new EzyConnectionCloseException("max request size"); + } + tcpAppBuffer = ByteBuffer.allocate(doubleSize); + break; + case BUFFER_UNDERFLOW: + break; + case CLOSED: + 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" + ); + 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 { + if (!handshaked.get()) { + throw new SSLException("not handshaked"); + } + ByteBuffer buffer = ByteBuffer.wrap(bytes); + ByteBuffer netBuffer = ByteBuffer.allocate(netBufferSize); + while (buffer.hasRemaining()) { + SSLEngineResult result = engine.wrap( + buffer, + netBuffer + ); + switch (result.getStatus()) { + case BUFFER_OVERFLOW: + netBuffer = ByteBuffer.allocate(netBuffer.capacity() * 2); + break; + case BUFFER_UNDERFLOW: + throw new IOException( + "should not happen, buffer underflow occurred after a wrap." + ); + case CLOSED: + 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" + ); + default: // OK + netBuffer.flip(); + byte[] answer = new byte[netBuffer.limit()]; + netBuffer.get(answer); + return answer; + } + } + return bytes; + } + + public void writeOrTimeout( + SocketChannel channel, + ByteBuffer buffer, + long timeoutAt + ) throws IOException { + buffer.flip(); + while (buffer.hasRemaining()) { + long currentTime = System.currentTimeMillis(); + if (currentTime >= timeoutAt) { + break; + } + channel.write(buffer); + } + } + + @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/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/EzyNioSocketReader.java b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzyNioSocketReader.java index cf828514..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 @@ -28,51 +28,39 @@ 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.getClass().getName(), e.getMessage()); - } - } - - private void handleAcceptableConnections() { - acceptableConnectionsHandler.handleAcceptableConnections(); - } - - private void doProcessReadyKeys() throws Exception { - int readyKeyCount = ownSelector.selectNow(); - if (readyKeyCount > 0) { - processReadyKeys(); + logger.info("I/O error at socket-reader", e); } } - 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); + socketDataReceiver.tcpReceive((SocketChannel) key.channel()); } } - - private void processWritableKey(SelectionKey key) { - key.interestOps(SelectionKey.OP_READ); - } - - private void processReadableKey(SelectionKey key) { - socketDataReceiver.tcpReceive((SocketChannel) key.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..24068f05 --- /dev/null +++ b/ezyfox-server-nio/src/main/java/com/tvd12/ezyfoxserver/nio/socket/EzySecureSocketDataReceiver.java @@ -0,0 +1,62 @@ +package com.tvd12.ezyfoxserver.nio.socket; + +import com.tvd12.ezyfoxserver.constant.EzyDisconnectReason; +import com.tvd12.ezyfoxserver.nio.handler.EzyNioHandlerGroup; +import com.tvd12.ezyfoxserver.socket.EzyChannel; + +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +public class EzySecureSocketDataReceiver extends EzySocketDataReceiver { + + public EzySecureSocketDataReceiver(Builder builder) { + super(builder); + } + + @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.isHandshaked()) { + try { + secureChannel.handshake(); + } catch (Throwable 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, + ByteBuffer buffer + ) throws Exception { + EzyNioSecureSocketChannel secureChannel = + (EzyNioSecureSocketChannel) channel; + return secureChannel.read(buffer); + } + + 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 2bba2e32..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 @@ -6,14 +6,15 @@ 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; import com.tvd12.ezyfoxserver.nio.wrapper.EzyHandlerGroupManager; +import com.tvd12.ezyfoxserver.socket.EzyChannel; 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; @@ -37,7 +38,7 @@ 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()); @@ -65,15 +66,14 @@ private void doTcpReceive(SocketChannel channel, ByteBuffer buffer) { tcpReadBytes(channel, buffer); } catch (Throwable e) { 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 ); } } - private void tcpReadBytes( + protected void tcpReadBytes( SocketChannel channel, ByteBuffer buffer ) throws Throwable { @@ -82,33 +82,47 @@ private 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) { - processReadBytes(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) { @@ -119,25 +133,24 @@ 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, - e.getClass().getName(), - e.getMessage() + "I/O error at udp-message-received (channel: {})", + channel, + e ); } @@ -161,10 +174,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 ); } } @@ -177,10 +189,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 ); } } @@ -205,7 +216,7 @@ private ExecutorService selectedExecutorService(Object channel) { return executorServices[index]; } - private int getMaxBufferSize() { + protected int getMaxBufferSize() { return EzyCoreConstants.MAX_READ_BUFFER_SIZE; } 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..e1593580 --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/EzySocketServerBootstrapTest.java @@ -0,0 +1,82 @@ +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.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.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); + + verify(socketSetting, times(1)).isCertificationSslActive(); + verify(socketSetting, times(1)).getSslHandshakeTimeout(); + verify(socketSetting, times(1)).getMaxRequestSize(); + verifyNoMoreInteractions(socketSetting); + verifyNoMoreInteractions(sslContext); + } + + @Test + public void newSocketAcceptorCaseCertificationSsl() { + // given + when(socketSetting.isCertificationSslActive()).thenReturn(true); + + // when + EzyNioSocketAcceptor acceptor = MethodInvoker.create() + .object(instance) + .method("newSocketAcceptor") + .invoke(EzyNioSocketAcceptor.class); + + // then + Asserts.assertEqualsType(acceptor, EzyNioSecureSocketAcceptor.class); + + verify(socketSetting, times(1)).isCertificationSslActive(); + verify(socketSetting, times(1)).getSslHandshakeTimeout(); + } +} 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..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 @@ -1,13 +1,28 @@ 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.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; +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 +39,114 @@ 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); + } + + @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/handler/EzyAbstractHandlerGroupTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/handler/EzyAbstractHandlerGroupTest.java index b683e558..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 @@ -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; @@ -77,32 +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); - MethodInvoker.create() - .object(group) - .method("canWriteBytes") - .param(EzyChannel.class, null) - .invoke(); - MethodInvoker.create() - .object(group) - .method("canWriteBytes") - .param(EzyChannel.class, channelX) - .invoke(); - 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()) @@ -263,7 +246,7 @@ public void executeSendingPacketCanNotWriteBytes() throws Exception { // then EzySession session = FieldUtil.getFieldValue(sut, "session"); - verify(session, times(3)).getChannel(); + verify(session, times(2)).getChannel(); } @Test @@ -289,6 +272,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 @@ -375,6 +417,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); @@ -403,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); @@ -418,8 +461,8 @@ public void executeSendingPacketWithTransportTypeWithUdpOrTcpActualUdp() throws Asserts.assertNotNull(group.getSession()); // then - verify(session, times(3)).getChannel(); - verify(session, times(2)).getDatagramChannelPool(); + verify(session, times(1)).isActivated(); + verify(session, times(2)).getChannel(); verify(session, times(1)).getUdpClientAddress(); } @@ -447,6 +490,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,11 +530,86 @@ 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(); } + @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 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..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 @@ -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; @@ -287,7 +290,7 @@ public void handleReceivedMessageHeaderIsNotRawBytes() throws Exception { // when MethodInvoker.create() .object(sut) - .method("handleReceivedMessage") + .method("fireMessageReceived") .param(EzyMessage.class, message) .call(); @@ -313,7 +316,7 @@ public void handleReceivedMessageStreamingNotEnable() throws Exception { // when MethodInvoker.create() .object(sut) - .method("handleReceivedMessage") + .method("fireMessageReceived") .param(EzyMessage.class, message) .call(); @@ -337,7 +340,7 @@ public void handleReceivedMessageSessionStreamingNotEnable() throws Exception { // when MethodInvoker.create() .object(sut) - .method("handleReceivedMessage") + .method("fireMessageReceived") .param(EzyMessage.class, message) .call(); @@ -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 { 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..4e50d2e7 --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketAcceptorTest.java @@ -0,0 +1,76 @@ +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 SSL_HANDSHAKE_TIMEOUT = 100; + private static final int MAX_REQUEST_SIZE = 1024; + + @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, + SSL_HANDSHAKE_TIMEOUT, + MAX_REQUEST_SIZE + ); + 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 new file mode 100644 index 00000000..27425694 --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzyNioSecureSocketChannelTest.java @@ -0,0 +1,1435 @@ +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.*; +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; +import static org.mockito.Mockito.*; + +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 SSL_HANDSHAKE_TIMEOUT = 100; + private static final int MAX_REQUEST_SIZE = 128; + + @BeforeMethod + public void setup() { + bytes = new byte[] {1, 2, 3}; + buffer = ByteBuffer.allocate(bufferSize); + netBuffer = ByteBuffer.allocate(bufferSize + 1); + sslContext = mock(SSLContext.class); + sslContextSpi = mock(EzySSLContextSpiForTest.class); + sslEngine = mock(SSLEngine.class); + sslSession = mock(SSLSession.class); + socketChannel = mock(SocketChannel.class); + instance = new EzyNioSecureSocketChannel( + socketChannel, + sslContext, + SSL_HANDSHAKE_TIMEOUT, + MAX_REQUEST_SIZE + ); + 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); + when(socketChannel.isConnected()).thenReturn(true); + } + + public void beforeNotHandshakeMethod() { + FieldUtil.setFieldValue(instance, "engine", sslEngine); + FieldUtil.setFieldValue(instance, "netBuffer", netBuffer); + FieldUtil.setFieldValue(instance, "appBufferSize", bufferSize); + FieldUtil.setFieldValue(instance, "netBufferSize", bufferSize); + AtomicBoolean handshaked = FieldUtil.getFieldValue(instance, "handshaked"); + handshaked.set(true); + } + + @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); + } + + 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(); + + verify(socketChannel, times(1)).isConnected(); + } + + @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()); + Asserts.assertNotNull(instance.getPackingLock()); + + 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 + Throwable e = Asserts.assertThrows(() -> instance.handshake()); + + // then + Asserts.assertEqualsType(e, EzyConnectionCloseException.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 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; + }); + + 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 handleCaseHandshakeStatusIsNeedWrapEngineStatusIsClosedWriteTimeout() throws Exception { + // given + when(sslEngine.getHandshakeStatus()).thenReturn( + SSLEngineResult.HandshakeStatus.NEED_WRAP + ); + when(socketChannel.write(any(ByteBuffer.class))).thenAnswer(it -> { + EzyThreads.sleep(SSL_HANDSHAKE_TIMEOUT * 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 + 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(SSL_HANDSHAKE_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.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 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 + beforeNotHandshakeMethod(); + + 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 packCaseBufferOverFlow() throws Exception { + // given + beforeNotHandshakeMethod(); + + 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 packCaseBufferUnderFlow() throws Exception { + // given + beforeNotHandshakeMethod(); + + 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 packCaseBufferClosed() 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); + + // 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 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 + beforeNotHandshakeMethod(); + + byte[] actual = instance.pack(new byte[0]); + + // then + 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 + 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 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 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 + 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(); + 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); + + // 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 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 + beforeNotHandshakeMethod(); + + buffer.clear(); + buffer.flip(); + + // when + byte[] actual = instance.read(buffer); + + // 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(); + } +} 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 9a681b6b..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; @@ -29,6 +30,7 @@ import com.tvd12.test.reflect.MethodInvoker; import org.testng.annotations.Test; +import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; @@ -37,7 +39,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; @@ -81,6 +82,24 @@ public void test() throws Exception { assert acceptableConnections.size() == 0; } + @Test + public void handleEventExceptionCase() throws Exception { + // given + EzyNioSocketAcceptor instance = new EzyNioSocketAcceptor(); + + 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(); @@ -182,6 +201,91 @@ 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(); + 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); + + // 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); + } + + @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(); + 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); + + // when + MethodInvoker.create() + .object(sut) + .method("acceptConnection") + .param(SocketChannel.class, clientChannel) + .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); } 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 883eda2b..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; @@ -35,7 +32,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; @@ -191,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() { diff --git a/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySSLContextSpiForTest.java b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySSLContextSpiForTest.java new file mode 100644 index 00000000..54ef5f39 --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySSLContextSpiForTest.java @@ -0,0 +1,10 @@ +package com.tvd12.ezyfoxserver.nio.testing.socket; + +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-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..35d4719d --- /dev/null +++ b/ezyfox-server-nio/src/test/java/com/tvd12/ezyfoxserver/nio/testing/socket/EzySecureSocketDataReceiverTest.java @@ -0,0 +1,174 @@ +package com.tvd12.ezyfoxserver.nio.testing.socket; + +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; +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 com.tvd12.test.util.RandomUtil; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import static org.mockito.Mockito.*; + +public class EzySecureSocketDataReceiverTest { + + private ByteBuffer buffer; + private EzyNioSecureSocketChannel channel; + private EzyHandlerGroupManager handlerGroupManager; + private EzySecureSocketDataReceiver instance; + + @BeforeMethod + public void setup() { + int bufferSize = 128; + buffer = ByteBuffer.allocate(bufferSize); + channel = mock(EzyNioSecureSocketChannel.class); + handlerGroupManager = mock(EzyHandlerGroupManager.class); + instance = (EzySecureSocketDataReceiver) EzySecureSocketDataReceiver + .builder() + .threadPoolSize(1) + .handlerGroupManager(handlerGroupManager) + .build(); + } + + @AfterMethod + public void verifyAll() { + verifyNoMoreInteractions(channel); + verifyNoMoreInteractions(handlerGroupManager); + } + + @Test + public void tcpReadBytesNormalCase() throws Exception { + // given + SocketChannel socketChannel = mock(SocketChannel.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + when(handlerGroupManager.getHandlerGroup(socketChannel)) + .thenReturn(handlerGroup); + when(handlerGroup.getChannel()).thenReturn(channel); + + // when + MethodInvoker.create() + .object(instance) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) + .param(ByteBuffer.class, buffer) + .invoke(); + + // then + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); + + verify(handlerGroup, times(1)).getChannel(); + verifyNoMoreInteractions(handlerGroup); + + verify(channel, times(1)).isHandshaked(); + verify(channel, times(1)).handshake(); + } + + @Test + public void tcpReadBytesHandshakeFailedCase() throws Exception { + // given + SocketChannel socketChannel = mock(SocketChannel.class); + EzyNioHandlerGroup handlerGroup = mock(EzyNioHandlerGroup.class); + when(handlerGroupManager.getHandlerGroup(socketChannel)) + .thenReturn(handlerGroup); + when(handlerGroup.getChannel()).thenReturn(channel); + + SSLException exception = new SSLException("test"); + doThrow(exception).when(channel).handshake(); + + // when + MethodInvoker.create() + .object(instance) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) + .param(ByteBuffer.class, buffer) + .invoke(); + + // then + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); + + verify(handlerGroup, times(1)).getChannel(); + verify(handlerGroup, times(1)).enqueueDisconnection( + EzyDisconnectReason.SSH_HANDSHAKE_FAILED + ); + verifyNoMoreInteractions(handlerGroup); + + verify(channel, times(1)).isHandshaked(); + verify(channel, times(1)).handshake(); + } + + @Test + public void tcpReadBytesAlreadyHandshakeCase() { + // given + 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 + MethodInvoker.create() + .object(instance) + .method("tcpReadBytes") + .param(SocketChannel.class, socketChannel) + .param(ByteBuffer.class, buffer) + .invoke(); + + // then + verify(handlerGroupManager, times(1)) + .getHandlerGroup(socketChannel); + + verify(handlerGroup, times(1)).getChannel(); + verifyNoMoreInteractions(handlerGroup); + + 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 + byte[] bytes = RandomUtil.randomShortByteArray(); + when(channel.read(buffer)).thenReturn(bytes); + + // when + byte[] actual = MethodInvoker.create() + .object(instance) + .method("readTcpBytesFromBuffer") + .param(EzyChannel.class, channel) + .param(ByteBuffer.class, buffer) + .invoke(byte[].class); + + // then + Asserts.assertEquals(actual, bytes); + verify(channel, times(1)).read(buffer); + } +} 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..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); } @@ -59,7 +65,7 @@ public void processReadBytesHandlerGroupIsNull() throws Exception { // when MethodInvoker.create() .object(sut) - .method("processReadBytes") + .method("processTcpReadBytes") .param(SocketChannel.class, channel) .param(ByteBuffer.class, buffer) .call(); @@ -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); + } }