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));
+ }
+}