You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2021/06/04 14:51:00 UTC

[shardingsphere] branch master updated: Support scaling with mysql 8 (#10654)

This is an automated email from the ASF dual-hosted git repository.

zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new ce15dbc  Support scaling with mysql 8 (#10654)
ce15dbc is described below

commit ce15dbc7dc3e151ae63f86bf8e1f86b8d45dd9c3
Author: avalon5666 <64...@users.noreply.github.com>
AuthorDate: Fri Jun 4 22:50:02 2021 +0800

    Support scaling with mysql 8 (#10654)
    
    * Refactor MySQLCommandPacketDecoder
    
    * Rename test
    
    * Support caching_sha2_password auth plugin
    
    * Compatible mysql 8 binlog table map event packet
---
 .../binlog/row/MySQLBinlogTableMapEventPacket.java |   4 +
 ...estPacket.java => MySQLAuthMoreDataPacket.java} |  23 ++--
 .../handshake/MySQLAuthSwitchRequestPacket.java    |  18 ++++
 .../handshake/MySQLAuthSwitchResponsePacket.java   |   8 +-
 .../scaling/mysql/client/MySQLClient.java          |   2 +
 .../scaling/mysql/client/PasswordEncryption.java   |  76 +++++++++++--
 .../client/netty/MySQLCommandPacketDecoder.java    |  19 +---
 .../mysql/client/netty/MySQLNegotiateHandler.java  |  43 +++++++-
 .../client/netty/MySQLNegotiatePackageDecoder.java |  75 +++++++++++++
 .../mysql/client/PasswordEncryptionTest.java       |  77 ++++++++++++++
 .../mysql/client/PasswordEncryptorTest.java        |  46 --------
 .../netty/MySQLCommandPacketDecoderTest.java       |  54 ----------
 .../netty/MySQLNegotiatePackageDecoderTest.java    | 118 +++++++++++++++++++++
 13 files changed, 428 insertions(+), 135 deletions(-)

diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/binlog/row/MySQLBinlogTableMapEventPacket.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/binlog/row/MySQLBinlogTableMapEventPacket.java
index d23789f..88b1746 100644
--- a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/binlog/row/MySQLBinlogTableMapEventPacket.java
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/binlog/row/MySQLBinlogTableMapEventPacket.java
@@ -63,6 +63,10 @@ public final class MySQLBinlogTableMapEventPacket extends AbstractMySQLBinlogEve
         readColumnDefs(payload);
         readColumnMetaDefs(payload);
         nullBitMap = new MySQLNullBitmap(columnCount, payload);
+        // mysql 8 binlog table map event include column type def
+        // for support lower than 8, read column type def by sql query
+        // so skip readable bytes here
+        payload.getByteBuf().skipBytes(payload.getByteBuf().readableBytes());
     }
     
     private void readColumnDefs(final MySQLPacketPayload payload) {
diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthMoreDataPacket.java
similarity index 66%
copy from shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java
copy to shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthMoreDataPacket.java
index f62ab92..005e885 100644
--- a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthMoreDataPacket.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.db.protocol.mysql.packet.handshake;
 
+import com.google.common.base.Preconditions;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.db.protocol.mysql.packet.MySQLPacket;
@@ -25,22 +26,30 @@ import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
 /**
  * MySQL authentication switch request packet.
  *
- * @see <a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest">AuthSwitchRequest</a>
+ * @see <a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthMoreData">AuthMoreData</a>
  */
 @RequiredArgsConstructor
-public final class MySQLAuthSwitchRequestPacket implements MySQLPacket {
+@Getter
+public final class MySQLAuthMoreDataPacket implements MySQLPacket {
+    
+    /**
+     * Header of MySQL auth more data packet.
+     */
+    public static final int HEADER = 0x01;
     
     @Getter
     private final int sequenceId;
     
-    private final String authPluginName;
+    private final byte[] pluginData;
     
-    private final MySQLAuthPluginData authPluginData;
+    public MySQLAuthMoreDataPacket(final MySQLPacketPayload payload) {
+        sequenceId = payload.readInt1();
+        Preconditions.checkArgument(HEADER == payload.readInt1(), "Header of MySQL auth more data packet must be `0x01`.");
+        pluginData = payload.readStringEOFByBytes();
+    }
     
     @Override
     public void write(final MySQLPacketPayload payload) {
-        payload.writeInt1(0xfe);
-        payload.writeStringNul(authPluginName);
-        payload.writeStringNul(new String(authPluginData.getAuthenticationPluginData()));
+        throw new UnsupportedOperationException();
     }
 }
diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java
index f62ab92..634509e 100644
--- a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchRequestPacket.java
@@ -17,11 +17,14 @@
 
 package org.apache.shardingsphere.db.protocol.mysql.packet.handshake;
 
+import com.google.common.base.Preconditions;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import org.apache.shardingsphere.db.protocol.mysql.packet.MySQLPacket;
 import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
 
+import java.util.Arrays;
+
 /**
  * MySQL authentication switch request packet.
  *
@@ -30,13 +33,28 @@ import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
 @RequiredArgsConstructor
 public final class MySQLAuthSwitchRequestPacket implements MySQLPacket {
     
+    /**
+     * Header of MySQL auth switch request packet.
+     */
+    public static final int HEADER = 0xfe;
+    
     @Getter
     private final int sequenceId;
     
     private final String authPluginName;
     
+    @Getter
     private final MySQLAuthPluginData authPluginData;
     
+    public MySQLAuthSwitchRequestPacket(final MySQLPacketPayload payload) {
+        sequenceId = payload.readInt1();
+        Preconditions.checkArgument(HEADER == payload.readInt1(), "Header of MySQL auth switch request packet must be `0xfe`.");
+        authPluginName = payload.readStringNul();
+        String strAuthPluginData = payload.readStringNul();
+        authPluginData = new MySQLAuthPluginData(Arrays.copyOfRange(strAuthPluginData.getBytes(), 0, 8),
+                Arrays.copyOfRange(strAuthPluginData.getBytes(), 8, 20));
+    }
+    
     @Override
     public void write(final MySQLPacketPayload payload) {
         payload.writeInt1(0xfe);
diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchResponsePacket.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchResponsePacket.java
index 7cd3811..7cb1ed6 100644
--- a/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchResponsePacket.java
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-mysql/src/main/java/org/apache/shardingsphere/db/protocol/mysql/packet/handshake/MySQLAuthSwitchResponsePacket.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.db.protocol.mysql.packet.handshake;
 
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.db.protocol.mysql.packet.MySQLPacket;
 import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
 
 /**
@@ -28,7 +29,7 @@ import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
  */
 @RequiredArgsConstructor
 @Getter
-public final class MySQLAuthSwitchResponsePacket {
+public final class MySQLAuthSwitchResponsePacket implements MySQLPacket {
     
     @Getter
     private final int sequenceId;
@@ -39,4 +40,9 @@ public final class MySQLAuthSwitchResponsePacket {
         sequenceId = payload.readInt1();
         authPluginResponse = payload.readStringEOFByBytes();
     }
+    
+    @Override
+    public void write(final MySQLPacketPayload payload) {
+        payload.writeBytes(authPluginResponse);
+    }
 }
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/MySQLClient.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/MySQLClient.java
index 1524328..416b3b4 100644
--- a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/MySQLClient.java
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/MySQLClient.java
@@ -42,6 +42,7 @@ import org.apache.shardingsphere.scaling.mysql.binlog.event.AbstractBinlogEvent;
 import org.apache.shardingsphere.scaling.mysql.client.netty.MySQLBinlogEventPacketDecoder;
 import org.apache.shardingsphere.scaling.mysql.client.netty.MySQLCommandPacketDecoder;
 import org.apache.shardingsphere.scaling.mysql.client.netty.MySQLNegotiateHandler;
+import org.apache.shardingsphere.scaling.mysql.client.netty.MySQLNegotiatePackageDecoder;
 
 import java.net.InetSocketAddress;
 import java.util.Objects;
@@ -82,6 +83,7 @@ public final class MySQLClient {
                     @Override
                     protected void initChannel(final SocketChannel socketChannel) {
                         socketChannel.pipeline().addLast(new PacketCodec(new MySQLPacketCodecEngine()));
+                        socketChannel.pipeline().addLast(new MySQLNegotiatePackageDecoder());
                         socketChannel.pipeline().addLast(new MySQLCommandPacketDecoder());
                         socketChannel.pipeline().addLast(new MySQLNegotiateHandler(connectInfo.getUsername(), connectInfo.getPassword(), responseCallback));
                         socketChannel.pipeline().addLast(new MySQLCommandResponseHandler());
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryption.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryption.java
index 9070deb..8fe4cb9 100644
--- a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryption.java
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryption.java
@@ -17,11 +17,18 @@
 
 package org.apache.shardingsphere.scaling.mysql.client;
 
+import com.google.common.primitives.Bytes;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
 
+import javax.crypto.Cipher;
+import java.security.KeyFactory;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
 
 /**
  * MySQL Password Encryption.
@@ -33,8 +40,8 @@ public final class PasswordEncryption {
      * Encrypt password with MySQL protocol 41.
      *
      * <p>
-     *     MySQL Internals Manual  /  MySQL Client/Server Protocol  /  Authentication Method  /  Secure Password Authentication
-     *     https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
+     * MySQL Internals Manual  /  MySQL Client/Server Protocol  /  Authentication Method  /  Secure Password Authentication
+     * https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
      * </p>
      *
      * @param password password
@@ -46,7 +53,62 @@ public final class PasswordEncryption {
         MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
         byte[] passwordSha1 = messageDigest.digest(password);
         byte[] concatSeed = concatSeed(messageDigest, seed, messageDigest.digest(passwordSha1));
-        return xorPassword(passwordSha1, concatSeed);
+        return xor(passwordSha1, concatSeed, concatSeed.length);
+    }
+    
+    /**
+     * Encrypt password with sha2.
+     *
+     * @param password password
+     * @param seed 20-bytes random data from server
+     * @return encrypted password
+     * @throws NoSuchAlgorithmException no such algorithm exception
+     */
+    public static byte[] encryptWithSha2(final byte[] password, final byte[] seed) throws NoSuchAlgorithmException {
+        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+        byte[] s1 = messageDigest.digest(password);
+        byte[] s2 = messageDigest.digest(s1);
+        messageDigest.reset();
+        messageDigest.update(s2);
+        messageDigest.update(seed);
+        byte[] s3 = messageDigest.digest();
+        messageDigest.reset();
+        return xor(s1, s3, s3.length);
+    }
+    
+    /**
+     * Encrypt password with rsa public key.
+     *
+     * @param password password
+     * @param seed 20-bytes random data from server
+     * @param transformation transformation
+     * @param publicKey public key
+     * @return encrypted password
+     */
+    public static byte[] encryptWithRSAPublicKey(final String password, final byte[] seed, final String transformation, final String publicKey) {
+        byte[] formattedPassword = password != null ? Bytes.concat(password.getBytes(), new byte[]{0}) : new byte[]{0};
+        return encryptWithRSAPublicKey(xor(formattedPassword, seed, formattedPassword.length), parseRSAPublicKey(publicKey), transformation);
+    }
+    
+    @SneakyThrows
+    private static byte[] encryptWithRSAPublicKey(final byte[] source, final RSAPublicKey key, final String transformation) {
+        Cipher cipher = Cipher.getInstance(transformation);
+        cipher.init(Cipher.ENCRYPT_MODE, key);
+        return cipher.doFinal(source);
+    }
+    
+    @SneakyThrows
+    private static RSAPublicKey parseRSAPublicKey(final String key) {
+        byte[] certificateData = Base64.getDecoder().decode(formatKey(key));
+        X509EncodedKeySpec spec = new X509EncodedKeySpec(certificateData);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        return (RSAPublicKey) kf.generatePublic(spec);
+    }
+    
+    private static byte[] formatKey(final String key) {
+        int start = key.indexOf("\n") + 1;
+        int end = key.lastIndexOf("\n");
+        return key.substring(start, end).replace("\n", "").getBytes();
     }
     
     private static byte[] concatSeed(final MessageDigest messageDigest, final byte[] seed, final byte[] passwordSha1) {
@@ -55,10 +117,10 @@ public final class PasswordEncryption {
         return messageDigest.digest();
     }
     
-    private static byte[] xorPassword(final byte[] passwordSha1, final byte[] concatSeed) {
-        byte[] result = new byte[concatSeed.length];
-        for (int i = 0; i < concatSeed.length; i++) {
-            result[i] = (byte) (concatSeed[i] ^ passwordSha1[i]);
+    private static byte[] xor(final byte[] data, final byte[] seed, final int length) {
+        byte[] result = new byte[length];
+        for (int i = 0; i < length; i++) {
+            result[i] = (byte) (seed[i] ^ data[i]);
         }
         return result;
     }
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoder.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoder.java
index 5070a39..dd73e68 100644
--- a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoder.java
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoder.java
@@ -20,14 +20,12 @@ package org.apache.shardingsphere.scaling.mysql.client.netty;
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.ByteToMessageDecoder;
-import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLAuthenticationMethod;
 import org.apache.shardingsphere.db.protocol.mysql.packet.command.query.MySQLColumnDefinition41Packet;
 import org.apache.shardingsphere.db.protocol.mysql.packet.command.query.MySQLFieldCountPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.command.query.text.MySQLTextResultSetRowPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLEofPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLErrPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLOKPacket;
-import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakePacket;
 import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
 import org.apache.shardingsphere.scaling.mysql.client.InternalResultSet;
 
@@ -42,19 +40,12 @@ public final class MySQLCommandPacketDecoder extends ByteToMessageDecoder {
     
     private States currentState = States.ResponsePacket;
     
-    private boolean authenticated;
-    
     private InternalResultSet internalResultSet;
     
     @Override
     protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) {
         MySQLPacketPayload payload = new MySQLPacketPayload(in);
-        if (!authenticated) {
-            out.add(decodeHandshakePacket(payload));
-            authenticated = true;
-        } else {
-            decodeCommandPacket(payload, out);
-        }
+        decodeCommandPacket(payload, out);
     }
     
     private void decodeCommandPacket(final MySQLPacketPayload payload, final List<Object> out) {
@@ -69,14 +60,6 @@ public final class MySQLCommandPacketDecoder extends ByteToMessageDecoder {
         decodeResponsePacket(payload, out);
     }
     
-    private MySQLHandshakePacket decodeHandshakePacket(final MySQLPacketPayload payload) {
-        MySQLHandshakePacket result = new MySQLHandshakePacket(payload);
-        if (!MySQLAuthenticationMethod.SECURE_PASSWORD_AUTHENTICATION.getMethodName().equals(result.getAuthPluginName())) {
-            throw new UnsupportedOperationException("Only supported SECURE_PASSWORD_AUTHENTICATION server");
-        }
-        return result;
-    }
-    
     private void decodeFieldPacket(final MySQLPacketPayload payload) {
         if (MySQLEofPacket.HEADER != (payload.getByteBuf().getByte(1) & 0xff)) {
             internalResultSet.getFieldDescriptors().add(new MySQLColumnDefinition41Packet(payload));
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiateHandler.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiateHandler.java
index 787ca7b..d666e60 100644
--- a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiateHandler.java
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiateHandler.java
@@ -26,6 +26,9 @@ import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLAuthenticationM
 import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLCapabilityFlag;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLErrPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLOKPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthMoreDataPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthSwitchRequestPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthSwitchResponsePacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakePacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakeResponse41Packet;
 import org.apache.shardingsphere.scaling.mysql.client.PasswordEncryption;
@@ -44,6 +47,10 @@ public final class MySQLNegotiateHandler extends ChannelInboundHandlerAdapter {
     
     private static final int CHARACTER_SET = 33;
     
+    private static final int REQUEST_PUBLIC_KEY = 2;
+    
+    private static final int PERFORM_FULL_AUTHENTICATION = 4;
+    
     private final String username;
     
     private final String password;
@@ -52,6 +59,11 @@ public final class MySQLNegotiateHandler extends ChannelInboundHandlerAdapter {
     
     private ServerInfo serverInfo;
     
+    private byte[] seed;
+    
+    private boolean publicKeyRequested;
+    
+    @SneakyThrows
     @Override
     public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
         if (msg instanceof MySQLHandshakePacket) {
@@ -66,6 +78,18 @@ public final class MySQLNegotiateHandler extends ChannelInboundHandlerAdapter {
             serverInfo.setServerVersion(new ServerVersion(handshake.getServerVersion()));
             return;
         }
+        if (msg instanceof MySQLAuthSwitchRequestPacket) {
+            MySQLAuthSwitchRequestPacket authSwitchRequest = (MySQLAuthSwitchRequestPacket) msg;
+            ctx.channel().writeAndFlush(new MySQLAuthSwitchResponsePacket(authSwitchRequest.getSequenceId() + 1,
+                    PasswordEncryption.encryptWithSha2(password.getBytes(), authSwitchRequest.getAuthPluginData().getAuthenticationPluginData())));
+            seed = authSwitchRequest.getAuthPluginData().getAuthenticationPluginData();
+            return;
+        }
+        if (msg instanceof MySQLAuthMoreDataPacket) {
+            MySQLAuthMoreDataPacket authMoreData = (MySQLAuthMoreDataPacket) msg;
+            handleCachingSha2Auth(ctx, authMoreData);
+            return;
+        }
         if (msg instanceof MySQLOKPacket) {
             ctx.channel().pipeline().remove(this);
             authResultCallback.setSuccess(serverInfo);
@@ -76,10 +100,25 @@ public final class MySQLNegotiateHandler extends ChannelInboundHandlerAdapter {
         throw new RuntimeException(error.getErrorMessage());
     }
     
+    private void handleCachingSha2Auth(final ChannelHandlerContext ctx, final MySQLAuthMoreDataPacket authMoreData) {
+        // how caching_sha2_password works: https://dev.mysql.com/doc/dev/mysql-server/8.0.11/page_caching_sha2_authentication_exchanges.html#sect_caching_sha2_info
+        if (!publicKeyRequested) {
+            if (PERFORM_FULL_AUTHENTICATION == authMoreData.getPluginData()[0]) {
+                publicKeyRequested = true;
+                ctx.channel().writeAndFlush(new MySQLAuthSwitchResponsePacket(authMoreData.getSequenceId() + 1, new byte[]{REQUEST_PUBLIC_KEY}));
+            }
+        } else {
+            ctx.channel().writeAndFlush(new MySQLAuthSwitchResponsePacket(authMoreData.getSequenceId() + 1,
+                    PasswordEncryption.encryptWithRSAPublicKey(password, seed,
+                            serverInfo.getServerVersion().greaterThanOrEqualTo(8, 0, 5) ? "RSA/ECB/OAEPWithSHA-1AndMGF1Padding" : "RSA/ECB/PKCS1Padding",
+                            new String(authMoreData.getPluginData()))));
+        }
+    }
+    
     private int generateClientCapability() {
         return MySQLCapabilityFlag.calculateCapabilityFlags(MySQLCapabilityFlag.CLIENT_LONG_PASSWORD, MySQLCapabilityFlag.CLIENT_LONG_FLAG,
-            MySQLCapabilityFlag.CLIENT_PROTOCOL_41, MySQLCapabilityFlag.CLIENT_INTERACTIVE, MySQLCapabilityFlag.CLIENT_TRANSACTIONS,
-            MySQLCapabilityFlag.CLIENT_SECURE_CONNECTION, MySQLCapabilityFlag.CLIENT_MULTI_STATEMENTS, MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH);
+                MySQLCapabilityFlag.CLIENT_PROTOCOL_41, MySQLCapabilityFlag.CLIENT_INTERACTIVE, MySQLCapabilityFlag.CLIENT_TRANSACTIONS,
+                MySQLCapabilityFlag.CLIENT_SECURE_CONNECTION, MySQLCapabilityFlag.CLIENT_MULTI_STATEMENTS, MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH);
     }
     
     @SneakyThrows(NoSuchAlgorithmException.class)
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiatePackageDecoder.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiatePackageDecoder.java
new file mode 100644
index 0000000..5215f47
--- /dev/null
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/main/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiatePackageDecoder.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.scaling.mysql.client.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import org.apache.shardingsphere.db.protocol.mysql.packet.MySQLPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLErrPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLOKPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthMoreDataPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthSwitchRequestPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakePacket;
+import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
+
+import java.util.List;
+
+/**
+ * MySQL negotiate package decoder.
+ */
+public final class MySQLNegotiatePackageDecoder extends ByteToMessageDecoder {
+    
+    private boolean handshakeReceived;
+    
+    @Override
+    protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) {
+        MySQLPacketPayload payload = new MySQLPacketPayload(in);
+        if (!handshakeReceived) {
+            out.add(decodeHandshakePacket(payload));
+            handshakeReceived = true;
+        } else {
+            MySQLPacket responsePacket = decodeResponsePacket(payload, out);
+            if (responsePacket instanceof MySQLOKPacket) {
+                ctx.channel().pipeline().remove(this);
+            }
+            out.add(responsePacket);
+        }
+    }
+    
+    private MySQLHandshakePacket decodeHandshakePacket(final MySQLPacketPayload payload) {
+        MySQLHandshakePacket result = new MySQLHandshakePacket(payload);
+        return result;
+    }
+    
+    private MySQLPacket decodeResponsePacket(final MySQLPacketPayload payload, final List<Object> out) {
+        int header = payload.getByteBuf().getByte(1) & 0xff;
+        switch (header) {
+            case MySQLErrPacket.HEADER:
+                return new MySQLErrPacket(payload);
+            case MySQLOKPacket.HEADER:
+                return new MySQLOKPacket(payload);
+            case MySQLAuthSwitchRequestPacket.HEADER:
+                return new MySQLAuthSwitchRequestPacket(payload);
+            case MySQLAuthMoreDataPacket.HEADER:
+                return new MySQLAuthMoreDataPacket(payload);
+            default:
+                throw new UnsupportedOperationException(String.format("Unsupported negotiate response header: %X", header));
+        }
+    }
+}
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryptionTest.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryptionTest.java
new file mode 100644
index 0000000..2d3dea0
--- /dev/null
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryptionTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.scaling.mysql.client;
+
+import lombok.SneakyThrows;
+import org.junit.Test;
+
+import java.security.NoSuchAlgorithmException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public final class PasswordEncryptionTest {
+    
+    @Test
+    public void assertEncryptWithMySQL41() throws NoSuchAlgorithmException {
+        byte[] passwordBytes = "password".getBytes();
+        byte[] seed = getRandomSeed();
+        assertThat(PasswordEncryption.encryptWithMySQL41(passwordBytes, seed), is(getMySQL41ExpectedPassword()));
+    }
+    
+    private byte[] getMySQL41ExpectedPassword() {
+        return new byte[]{-110, -31, 48, -32, -22, -29, 54, -40, 54, 118, -119, -16, -96, -25, 121, -64, -75, -103, 73, -44};
+    }
+    
+    @SneakyThrows(NoSuchAlgorithmException.class)
+    @Test
+    public void encryptEncryptWithSha2() {
+        assertThat(PasswordEncryption.encryptWithSha2("123456".getBytes(), getRandomSeed()), is(getSha2ExpectedPassword()));
+    }
+    
+    private byte[] getSha2ExpectedPassword() {
+        return new byte[]{-47, -106, -46, 74, 24, 12, 49, 33, 47, -65, -43, -23, -43, 4, -107, 103, -4, 63, -88, 67, 118, 29, -4, -9, 15, -123, 94, -116, 106, -121, 11, 29};
+    }
+    
+    private byte[] getRandomSeed() {
+        byte[] result = new byte[20];
+        for (int i = 0; i < result.length; i++) {
+            result[i] = (byte) i;
+        }
+        return result;
+    }
+    
+    @Test
+    public void assertEncryptWithRSAPublicKey() {
+        PasswordEncryption.encryptWithRSAPublicKey("123456", getRandomSeed(),
+                "RSA/ECB/OAEPWithSHA-1AndMGF1Padding",
+                mockPublicKey());
+    }
+    
+    private String mockPublicKey() {
+        return "-----BEGIN PUBLIC KEY-----\n"
+                + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ealW/qDdArgzCMnE5Cz\n"
+                + "6FHskcTTweMncG6A124rn2DFBZvmZNyTBiFLM7Scp3jFSyqpw2xg6aaKcM9eCaCf\n"
+                + "nJg4A18HgpAxrFnijVADgsNrHlSniNe2AsN+/uLpEtWezVLr823WvPLgMKQMRWfy\n"
+                + "UD24rpoC2Leir+rvyG8xbDHX65NPGxPFGrlwo7kbUqrgQlYOC3x64C4/S/6K6EZQ\n"
+                + "XaUZwZHdXjEme0/D8p8KBXdMipanZXwHdL+LOBSACj3/FwHn+6oZO2k02g80uofs\n"
+                + "zFdWMjpPVqVCqe85GRFzEY73wDYEItl0d+9a9OV3FFZqVgC2FLk3cD5qajPtyo6v\n"
+                + "UQIDAQAB\n"
+                + "-----END PUBLIC KEY-----";
+    }
+}
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryptorTest.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryptorTest.java
deleted file mode 100644
index 1c28f93..0000000
--- a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/PasswordEncryptorTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.shardingsphere.scaling.mysql.client;
-
-import org.junit.Test;
-import java.security.NoSuchAlgorithmException;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-public final class PasswordEncryptorTest {
-    
-    @Test
-    public void assertEncryptWithMySQL41() throws NoSuchAlgorithmException {
-        byte[] passwordBytes = "password".getBytes();
-        byte[] seed = getRandomSeed();
-        assertThat(PasswordEncryption.encryptWithMySQL41(passwordBytes, seed), is(getExpectedPassword()));
-    }
-    
-    private byte[] getRandomSeed() {
-        byte[] result = new byte[20];
-        for (int i = 0; i < result.length; i++) {
-            result[i] = (byte) i;
-        }
-        return result;
-    }
-    
-    private byte[] getExpectedPassword() {
-        return new byte[] {-110, -31, 48, -32, -22, -29, 54, -40, 54, 118, -119, -16, -96, -25, 121, -64, -75, -103, 73, -44};
-    }
-}
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoderTest.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoderTest.java
index eb5a28b..3af395c 100644
--- a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoderTest.java
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLCommandPacketDecoderTest.java
@@ -18,15 +18,9 @@
 package org.apache.shardingsphere.scaling.mysql.client.netty;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
-import io.netty.buffer.Unpooled;
-import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLServerInfo;
-import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLStatusFlag;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLEofPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLErrPacket;
 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLOKPacket;
-import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakePacket;
-import org.apache.shardingsphere.scaling.core.util.ReflectionUtil;
 import org.apache.shardingsphere.scaling.mysql.client.InternalResultSet;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,56 +41,10 @@ public final class MySQLCommandPacketDecoderTest {
     @Mock
     private ByteBuf byteBuf;
     
-    @Test(expected = IllegalArgumentException.class)
-    public void assertDecodeUnsupportedProtocolVersion() {
-        MySQLCommandPacketDecoder commandPacketDecoder = new MySQLCommandPacketDecoder();
-        commandPacketDecoder.decode(null, byteBuf, null);
-    }
-    
-    @Test(expected = UnsupportedOperationException.class)
-    public void assertDecodeUnsupportedAuthenticationMethod() {
-        when(byteBuf.readUnsignedByte()).thenReturn((short) 0, (short) MySQLServerInfo.PROTOCOL_VERSION);
-        when(byteBuf.readUnsignedShortLE()).thenReturn(MySQLStatusFlag.SERVER_STATUS_AUTOCOMMIT.getValue());
-        MySQLCommandPacketDecoder commandPacketDecoder = new MySQLCommandPacketDecoder();
-        commandPacketDecoder.decode(null, byteBuf, null);
-    }
-    
-    @Test
-    public void assertDecodeHandshakePacket() {
-        MySQLCommandPacketDecoder commandPacketDecoder = new MySQLCommandPacketDecoder();
-        List<Object> actual = new LinkedList<>();
-        commandPacketDecoder.decode(null, mockHandshakePacket(), actual);
-        assertHandshakePacket(actual);
-    }
-    
-    private ByteBuf mockHandshakePacket() {
-        String handshakePacket = "000a352e372e32312d6c6f6700090000004a592a1f725a0d0900fff7210200ff8115000000000000000000001a437b30323a4d2b514b5870006d"
-            + "7973716c5f6e61746976655f70617373776f72640000000002000000";
-        byte[] handshakePacketBytes = ByteBufUtil.decodeHexDump(handshakePacket);
-        ByteBuf result = Unpooled.buffer(handshakePacketBytes.length);
-        result.writeBytes(handshakePacketBytes);
-        return result;
-    }
-    
-    private void assertHandshakePacket(final List<Object> actual) {
-        assertThat(actual.size(), is(1));
-        assertThat(actual.get(0), instanceOf(MySQLHandshakePacket.class));
-        MySQLHandshakePacket actualPacket = (MySQLHandshakePacket) actual.get(0);
-        assertThat(actualPacket.getProtocolVersion(), is(0x0a));
-        assertThat(actualPacket.getServerVersion(), is("5.7.21-log"));
-        assertThat(actualPacket.getConnectionId(), is(9));
-        assertThat(actualPacket.getCharacterSet(), is(33));
-        assertThat(actualPacket.getStatusFlag().getValue(), is(2));
-        assertThat(actualPacket.getCapabilityFlagsLower(), is(63487));
-        assertThat(actualPacket.getCapabilityFlagsUpper(), is(33279));
-        assertThat(actualPacket.getAuthPluginName(), is("mysql_native_password"));
-    }
-    
     @Test
     public void assertDecodeOkPacket() throws NoSuchFieldException, IllegalAccessException {
         MySQLCommandPacketDecoder commandPacketDecoder = new MySQLCommandPacketDecoder();
         List<Object> actual = new LinkedList<>();
-        ReflectionUtil.setFieldValue(commandPacketDecoder, "authenticated", true);
         commandPacketDecoder.decode(null, mockOkPacket(), actual);
         assertPacketByType(actual, MySQLOKPacket.class);
     }
@@ -111,7 +59,6 @@ public final class MySQLCommandPacketDecoderTest {
     public void assertDecodeErrPacket() throws NoSuchFieldException, IllegalAccessException {
         MySQLCommandPacketDecoder commandPacketDecoder = new MySQLCommandPacketDecoder();
         List<Object> actual = new LinkedList<>();
-        ReflectionUtil.setFieldValue(commandPacketDecoder, "authenticated", true);
         commandPacketDecoder.decode(null, mockErrPacket(), actual);
         assertPacketByType(actual, MySQLErrPacket.class);
     }
@@ -126,7 +73,6 @@ public final class MySQLCommandPacketDecoderTest {
     public void assertDecodeQueryCommPacket() throws NoSuchFieldException, IllegalAccessException {
         MySQLCommandPacketDecoder commandPacketDecoder = new MySQLCommandPacketDecoder();
         List<Object> actual = new LinkedList<>();
-        ReflectionUtil.setFieldValue(commandPacketDecoder, "authenticated", true);
         commandPacketDecoder.decode(null, mockEmptyResultSetPacket(), actual);
         commandPacketDecoder.decode(null, mockFieldDefinition41Packet(), actual);
         commandPacketDecoder.decode(null, mockEofPacket(), actual);
diff --git a/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiatePackageDecoderTest.java b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiatePackageDecoderTest.java
new file mode 100644
index 0000000..1e3511a
--- /dev/null
+++ b/shardingsphere-scaling/shardingsphere-scaling-dialect/shardingsphere-scaling-mysql/src/test/java/org/apache/shardingsphere/scaling/mysql/client/netty/MySQLNegotiatePackageDecoderTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.scaling.mysql.client.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthMoreDataPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthSwitchRequestPacket;
+import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakePacket;
+import org.apache.shardingsphere.scaling.core.util.ReflectionUtil;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class MySQLNegotiatePackageDecoderTest {
+    
+    @Mock
+    private ByteBuf byteBuf;
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assertDecodeUnsupportedProtocolVersion() {
+        MySQLNegotiatePackageDecoder commandPacketDecoder = new MySQLNegotiatePackageDecoder();
+        commandPacketDecoder.decode(null, byteBuf, null);
+    }
+    
+    @Test
+    public void assertDecodeHandshakePacket() {
+        MySQLNegotiatePackageDecoder commandPacketDecoder = new MySQLNegotiatePackageDecoder();
+        List<Object> actual = new LinkedList<>();
+        commandPacketDecoder.decode(null, mockHandshakePacket(), actual);
+        assertHandshakePacket(actual);
+    }
+    
+    private ByteBuf mockHandshakePacket() {
+        String handshakePacket = "000a352e372e32312d6c6f6700090000004a592a1f725a0d0900fff7210200ff8115000000000000000000001a437b30323a4d2b514b5870006d"
+                + "7973716c5f6e61746976655f70617373776f72640000000002000000";
+        byte[] handshakePacketBytes = ByteBufUtil.decodeHexDump(handshakePacket);
+        ByteBuf result = Unpooled.buffer(handshakePacketBytes.length);
+        result.writeBytes(handshakePacketBytes);
+        return result;
+    }
+    
+    private void assertHandshakePacket(final List<Object> actual) {
+        assertThat(actual.size(), is(1));
+        assertThat(actual.get(0), instanceOf(MySQLHandshakePacket.class));
+        MySQLHandshakePacket actualPacket = (MySQLHandshakePacket) actual.get(0);
+        assertThat(actualPacket.getProtocolVersion(), is(0x0a));
+        assertThat(actualPacket.getServerVersion(), is("5.7.21-log"));
+        assertThat(actualPacket.getConnectionId(), is(9));
+        assertThat(actualPacket.getCharacterSet(), is(33));
+        assertThat(actualPacket.getStatusFlag().getValue(), is(2));
+        assertThat(actualPacket.getCapabilityFlagsLower(), is(63487));
+        assertThat(actualPacket.getCapabilityFlagsUpper(), is(33279));
+        assertThat(actualPacket.getAuthPluginName(), is("mysql_native_password"));
+    }
+    
+    @Test
+    public void assertDecodeAuthSwitchRequestPacket() throws NoSuchFieldException, IllegalAccessException {
+        MySQLNegotiatePackageDecoder negotiatePackageDecoder = new MySQLNegotiatePackageDecoder();
+        ReflectionUtil.setFieldValue(negotiatePackageDecoder, "handshakeReceived", true);
+        List<Object> actual = new LinkedList<>();
+        negotiatePackageDecoder.decode(null, authSwitchRequestPacket(), actual);
+        assertPacketByType(actual, MySQLAuthSwitchRequestPacket.class);
+    }
+    
+    private ByteBuf authSwitchRequestPacket() {
+        when(byteBuf.readUnsignedByte()).thenReturn((short) 0, (short) MySQLAuthSwitchRequestPacket.HEADER);
+        when(byteBuf.getByte(1)).thenReturn((byte) MySQLAuthSwitchRequestPacket.HEADER);
+        when(byteBuf.bytesBefore((byte) 0)).thenReturn(20);
+        return byteBuf;
+    }
+    
+    @Test
+    public void assertDecodeAuthMoreDataPacket() throws NoSuchFieldException, IllegalAccessException {
+        MySQLNegotiatePackageDecoder negotiatePackageDecoder = new MySQLNegotiatePackageDecoder();
+        ReflectionUtil.setFieldValue(negotiatePackageDecoder, "handshakeReceived", true);
+        List<Object> actual = new LinkedList<>();
+        negotiatePackageDecoder.decode(null, authMoreDataPacket(), actual);
+        assertPacketByType(actual, MySQLAuthMoreDataPacket.class);
+    }
+    
+    private ByteBuf authMoreDataPacket() {
+        when(byteBuf.readUnsignedByte()).thenReturn((short) 0, (short) MySQLAuthMoreDataPacket.HEADER);
+        when(byteBuf.getByte(1)).thenReturn((byte) MySQLAuthMoreDataPacket.HEADER);
+        return byteBuf;
+    }
+    
+    private void assertPacketByType(final List<Object> actual, final Class<?> clazz) {
+        assertThat(actual.size(), is(1));
+        assertThat(actual.get(0), instanceOf(clazz));
+    }
+}