├── .gitignore ├── sql-parser ├── src │ └── main │ │ └── java │ │ └── DeleteMe.java └── pom.xml ├── codec ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── mheath │ │ │ └── netty │ │ │ └── codec │ │ │ └── mysql │ │ │ ├── MysqlClientPacketDecoder.java │ │ │ ├── MysqlServerPacketDecoder.java │ │ │ ├── MysqlPacket.java │ │ │ ├── MysqlClientPacket.java │ │ │ ├── MysqlServerPacket.java │ │ │ ├── AbstractMySqlPacket.java │ │ │ ├── QueryCommand.java │ │ │ ├── ColumnCount.java │ │ │ ├── CommandPacket.java │ │ │ ├── ColumnFlag.java │ │ │ ├── AbstractAuthPluginDataBuilder.java │ │ │ ├── ServerStatusFlag.java │ │ │ ├── MysqlClientCommandPacketDecoder.java │ │ │ ├── ResultsetRow.java │ │ │ ├── ErrorResponse.java │ │ │ ├── Constants.java │ │ │ ├── AbstractCapabilitiesBuilder.java │ │ │ ├── AbstractPacketEncoder.java │ │ │ ├── EofResponse.java │ │ │ ├── Command.java │ │ │ ├── ColumnType.java │ │ │ ├── MysqlNativePasswordUtil.java │ │ │ ├── CapabilityFlags.java │ │ │ ├── MysqlClientConnectionPacketDecoder.java │ │ │ ├── AbstractPacketDecoder.java │ │ │ ├── MysqlClientPacketEncoder.java │ │ │ ├── OkResponse.java │ │ │ ├── MysqlServerConnectionPacketDecoder.java │ │ │ ├── HandshakeResponse.java │ │ │ ├── ColumnDefinition.java │ │ │ ├── Handshake.java │ │ │ ├── MysqlServerResultSetPacketDecoder.java │ │ │ ├── MysqlServerPacketEncoder.java │ │ │ ├── CodecUtils.java │ │ │ └── MysqlCharacterSet.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── mheath │ │ └── netty │ │ └── codec │ │ └── mysql │ │ ├── DeleteMe.java │ │ ├── ResponsesDecodeEncodeTest.java │ │ ├── integration │ │ ├── server │ │ │ ├── JdbcClientTest.java │ │ │ └── TestServer.java │ │ └── client │ │ │ ├── HandshakeTest.java │ │ │ ├── SimpleQueryTest.java │ │ │ └── AbstractIntegrationTest.java │ │ ├── TestUtils.java │ │ ├── ByteBufAssert.java │ │ ├── MessageDecodeEncodeTester.java │ │ ├── CodecUtilsTest.java │ │ ├── MysqlPacketAssert.java │ │ └── HandshakeDecodeEncodeTest.java └── pom.xml ├── examples └── pom.xml ├── README.md ├── pom.xml ├── mvnw.cmd ├── mvnw └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | *.iml 4 | 5 | -------------------------------------------------------------------------------- /sql-parser/src/main/java/DeleteMe.java: -------------------------------------------------------------------------------- 1 | package PACKAGE_NAME; 2 | 3 | /** 4 | * 5 | */ 6 | public class DeleteMe { 7 | } 8 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlClientPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | 5 | /** 6 | * 7 | */ 8 | public interface MysqlClientPacketDecoder extends ChannelHandler { 9 | } 10 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlServerPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | 5 | /** 6 | * 7 | */ 8 | public interface MysqlServerPacketDecoder extends ChannelHandler { 9 | } 10 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/DeleteMe.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * 9 | */ 10 | public class DeleteMe { 11 | 12 | private static Pattern SETTINGS_PATTERN = Pattern.compile("@@(\\w+)\\sAS\\s(\\w+)"); 13 | 14 | public static void main(String[] args) throws Exception { 15 | Connection conn = DriverManager.getConnection("jdbc:mysql://localhost?useSSL=false", "root", ""); 16 | conn.close(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.github.mheath 6 | netty-codec-mysql-parent 7 | 0.1-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | 12 | netty-codec-mysql-examples 13 | jar 14 | 15 | Netty MySQL Codec Examples 16 | Examples for using the MySQL Netty Codec 17 | 18 | 19 | 20 | com.github.mheath 21 | netty-codec-mysql 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | public interface MysqlPacket { 20 | 21 | int getSequenceId(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlClientPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | public interface MysqlClientPacket extends MysqlPacket { 23 | } 24 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlServerPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | public interface MysqlServerPacket extends MysqlPacket { 23 | } 24 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/ResponsesDecodeEncodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | public class ResponsesDecodeEncodeTest { 23 | 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netty MySQL Codec (Alpha) 2 | 3 | Netty MySQL Codec provides Netty based MySQL/MariaDB encoders and decoders for use with both clients and servers. 4 | 5 | ## Implemented Features 6 | * Capability negotiation 7 | * Secure password authentication 8 | * Text Protocol (query/result set encoding/decoding and misc. server commands) 9 | 10 | ## Road Map 11 | * Binary protocol support (prepared statements) 12 | * Security 13 | * SSL 14 | * SHA256 authentication 15 | * Protocol level compression 16 | * Stored procedures 17 | * Replication protocols 18 | 19 | ## How to build 20 | 21 | This project uses [Maven Wrapper](https://github.com/takari/maven-wrapper). To build it, ensure you have at least JDK 22 | 8 installed and simply run `./mvnw install` or `./mvnw.cmd install` on Windows. 23 | 24 | ## Running integration tests 25 | 26 | TODO Document how to run the integration tests 27 | 28 | ## Examples 29 | TODO Implement examples - client and server 30 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/AbstractMySqlPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | abstract class AbstractMySqlPacket implements MysqlPacket { 23 | 24 | private final int sequenceId; 25 | 26 | public AbstractMySqlPacket(int sequenceId) { 27 | this.sequenceId = sequenceId; 28 | } 29 | 30 | @Override 31 | public int getSequenceId() { 32 | return 0; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/QueryCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | public class QueryCommand extends CommandPacket { 23 | 24 | private final String query; 25 | 26 | public QueryCommand(int sequenceId, String query) { 27 | super(sequenceId, Command.COM_QUERY); 28 | this.query = query; 29 | } 30 | 31 | public String getQuery() { 32 | return query; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ColumnCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | public class ColumnCount extends AbstractMySqlPacket implements MysqlServerPacket { 23 | 24 | final int fieldCount; 25 | 26 | public ColumnCount(int sequenceId, int fieldCount) { 27 | super(sequenceId); 28 | this.fieldCount = fieldCount; 29 | } 30 | 31 | public int getFieldCount() { 32 | return fieldCount; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/CommandPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * 21 | */ 22 | public class CommandPacket extends AbstractMySqlPacket implements MysqlClientPacket { 23 | 24 | private final Command command; 25 | 26 | public CommandPacket(int sequenceId, Command command) { 27 | super(sequenceId); 28 | this.command = command; 29 | } 30 | 31 | public Command getCommand() { 32 | return command; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ColumnFlag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * Field flags. 21 | *

22 | * {@see http://dev.mysql.com/doc/refman/5.7/en/c-api-data-structures.html} 23 | */ 24 | public enum ColumnFlag { 25 | NOT_NULL, 26 | PRI_KEY, 27 | UNIQUE_KEY, 28 | MULTIPLE_KEY, 29 | UNSIGNED, 30 | ZEROFILL, 31 | BINARY, 32 | AUTO_INCREMENT, 33 | ENUM, 34 | SET, 35 | BLOB, 36 | TIMESTAMP, 37 | NUM, 38 | NO_DEFAULT_VALUE, 39 | UNKNOWN14, 40 | UNKNOWN15 41 | } -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/AbstractAuthPluginDataBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.Unpooled; 21 | 22 | /** 23 | * 24 | */ 25 | abstract class AbstractAuthPluginDataBuilder 26 | extends AbstractCapabilitiesBuilder { 27 | protected final ByteBuf authPluginData = Unpooled.buffer(); 28 | 29 | public B addAuthData(byte[] bytes) { 30 | authPluginData.writeBytes(bytes); 31 | return (B) this; 32 | } 33 | 34 | public B addAuthData(ByteBuf buf, int length) { 35 | authPluginData.writeBytes(buf, length); 36 | return (B) this; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ServerStatusFlag.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * The MySQL client/server capability flags. 21 | * 22 | * @see Server Status Flags Reference 23 | * Documentation 24 | */ 25 | public enum ServerStatusFlag { 26 | IN_TRANSACTION, 27 | AUTO_COMMIT, 28 | MORE_RESULTS_EXIST, 29 | NO_GOOD_INDEX_USED, 30 | CURSOR_EXISTS, 31 | LAST_ROW_SENT, 32 | DATABASE_DROPPED, 33 | NO_BACKSLASH_ESCAPES, 34 | METADATA_CHANGED, 35 | QUERY_WAS_SLOW, 36 | PREPARED_STATEMENT_OUT_PARAMS, 37 | IN_READONLY_TRANSACTION, 38 | SESSION_STATE_CHANGED, 39 | UNKNOWN_13, 40 | UNKNOWN_14, 41 | UNKNOWN_15 42 | } 43 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlClientCommandPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.DecoderException; 9 | 10 | /** 11 | * 12 | */ 13 | public class MysqlClientCommandPacketDecoder extends AbstractPacketDecoder implements MysqlClientPacketDecoder { 14 | 15 | public MysqlClientCommandPacketDecoder() { 16 | this(Constants.DEFAULT_MAX_PACKET_SIZE); 17 | } 18 | 19 | public MysqlClientCommandPacketDecoder(int maxPacketSize) { 20 | super(maxPacketSize); 21 | } 22 | 23 | @Override 24 | protected void decodePacket(ChannelHandlerContext ctx, int sequenceId, ByteBuf packet, List out) { 25 | final MysqlCharacterSet clientCharset = MysqlCharacterSet.getClientCharsetAttr(ctx.channel()); 26 | 27 | final byte commandCode = packet.readByte(); 28 | final Optional command = Command.findByCommandCode(commandCode); 29 | if (!command.isPresent()) { 30 | throw new DecoderException("Unknown command " + commandCode); 31 | } 32 | switch (command.get()) { 33 | case COM_QUERY: 34 | out.add(new QueryCommand(sequenceId, CodecUtils.readFixedLengthString(packet, packet.readableBytes(), clientCharset.getCharset()))); 35 | break; 36 | default: 37 | out.add(new CommandPacket(sequenceId, command.get())); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ResultsetRow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collection; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | /** 25 | * 26 | */ 27 | public class ResultsetRow extends AbstractMySqlPacket implements MysqlServerPacket { 28 | private final List values = new ArrayList<>(); 29 | 30 | public ResultsetRow(int sequenceId, String... values) { 31 | super(sequenceId); 32 | Collections.addAll(this.values, values); 33 | } 34 | 35 | public ResultsetRow(int sequenceId, Collection values) { 36 | super(sequenceId); 37 | this.values.addAll(values); 38 | } 39 | 40 | public List getValues() { 41 | return values; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * This packet indicates that an error occurred. 21 | */ 22 | public class ErrorResponse extends AbstractMySqlPacket implements MysqlServerPacket { 23 | 24 | private final int errorNumber; 25 | private final byte[] sqlState; 26 | private final String message; 27 | 28 | public ErrorResponse(int sequenceId, int errorNumber, byte[] sqlState, String message) { 29 | super(sequenceId); 30 | this.errorNumber = errorNumber; 31 | this.sqlState = sqlState; 32 | this.message = message; 33 | } 34 | 35 | public int getErrorNumber() { 36 | return errorNumber; 37 | } 38 | 39 | public byte[] getSqlState() { 40 | return sqlState; 41 | } 42 | 43 | public String getMessage() { 44 | return message; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.EnumSet; 20 | import java.util.Set; 21 | 22 | /** 23 | * 24 | */ 25 | public interface Constants { 26 | 27 | int NUL_BYTE = 0x00; 28 | 29 | int RESPONSE_OK = 0x00; 30 | int RESPONSE_EOF = 0xfe; 31 | int RESPONSE_ERROR = 0xff; 32 | 33 | int MINIMUM_SUPPORTED_PROTOCOL_VERSION = 10; 34 | 35 | int SQL_STATE_SIZE = 6; 36 | 37 | // Handshake constants 38 | int AUTH_PLUGIN_DATA_PART1_LEN = 8; 39 | int AUTH_PLUGIN_DATA_PART2_MIN_LEN = 13; 40 | int AUTH_PLUGIN_DATA_MIN_LEN = AUTH_PLUGIN_DATA_PART1_LEN + AUTH_PLUGIN_DATA_PART2_MIN_LEN; 41 | int HANDSHAKE_RESERVED_BYTES = 10; 42 | 43 | // Auth plugins 44 | String MYSQL_NATIVE_PASSWORD = "mysql_native_password"; 45 | 46 | int DEFAULT_MAX_PACKET_SIZE = 1048576; 47 | } 48 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/AbstractCapabilitiesBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.Set; 22 | 23 | /** 24 | * 25 | */ 26 | abstract class AbstractCapabilitiesBuilder { 27 | final Set capabilities = CapabilityFlags.getImplicitCapabilities(); 28 | 29 | public B addCapabilities(CapabilityFlags... capabilities) { 30 | Collections.addAll(this.capabilities, capabilities); 31 | return (B) this; 32 | } 33 | 34 | public B addCapabilities(Collection capabilities) { 35 | this.capabilities.addAll(capabilities); 36 | return (B) this; 37 | } 38 | 39 | public boolean hasCapability(CapabilityFlags capability) { 40 | return capabilities.contains(capability); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/integration/server/JdbcClientTest.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql.integration.server; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLWarning; 7 | import java.sql.Statement; 8 | 9 | import org.assertj.core.api.Assertions; 10 | import org.junit.jupiter.api.AfterAll; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.Disabled; 13 | import org.junit.jupiter.api.Test; 14 | 15 | /** 16 | * 17 | */ 18 | public class JdbcClientTest { 19 | 20 | private static TestServer server; 21 | private static final int port = Integer.valueOf(System.getProperty("server.port", "37294")); 22 | 23 | @BeforeAll 24 | public static void startServer() { 25 | server = new TestServer(port); 26 | } 27 | 28 | @AfterAll 29 | public static void stopServer() { 30 | server.close(); 31 | } 32 | 33 | @Test 34 | @Disabled 35 | public void connectAndQuery() throws Exception { 36 | try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:" + server.getPort() + "/test", server.getUser(), server.getPassword())) { 37 | Assertions.assertThat(conn.isClosed()).isFalse(); 38 | final SQLWarning warnings = conn.getWarnings(); 39 | try (Statement statement = conn.createStatement()) { 40 | try (ResultSet rs = statement.executeQuery("SELECT 1")) { 41 | while (rs.next()) { 42 | System.out.println(rs.getString(1)); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/TestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.Unpooled; 21 | 22 | /** 23 | * 24 | */ 25 | public class TestUtils { 26 | 27 | public static ByteBuf toBuf(int... values) { 28 | final ByteBuf buf = Unpooled.buffer(values.length); 29 | for (int i = 0; i < values.length; i++) { 30 | buf.writeByte(values[i]); 31 | } 32 | return buf; 33 | } 34 | 35 | public static ByteBuf toBuf(String hexStream) { 36 | hexStream = hexStream.replaceAll("\\s+", ""); 37 | final ByteBuf buf = Unpooled.buffer(hexStream.length() / 2); 38 | for (int i = 0; i < hexStream.length(); i += 2) { 39 | final String sub = hexStream.substring(i, i + 2); 40 | final int b = Integer.valueOf(sub, 16); 41 | buf.writeByte(b); 42 | } 43 | return buf; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/AbstractPacketEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.handler.codec.MessageToByteEncoder; 22 | 23 | /** 24 | * 25 | */ 26 | public abstract class AbstractPacketEncoder extends MessageToByteEncoder { 27 | 28 | @Override 29 | final protected void encode(ChannelHandlerContext ctx, T packet, ByteBuf buf) throws Exception { 30 | final int writerIdx = buf.writerIndex(); 31 | buf.writeInt(0); // Advance the writer index so we can set the packet length after encoding 32 | encodePacket(ctx, packet, buf); 33 | final int len = buf.writerIndex() - writerIdx - 4; 34 | buf.setMediumLE(writerIdx, len) 35 | .setByte(writerIdx + 3, packet.getSequenceId()); 36 | } 37 | 38 | protected abstract void encodePacket(ChannelHandlerContext ctx, T packet, ByteBuf buf) throws Exception; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/ByteBufAssert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufUtil; 21 | import org.assertj.core.api.AbstractAssert; 22 | 23 | /** 24 | * 25 | */ 26 | public class ByteBufAssert extends AbstractAssert { 27 | 28 | public static ByteBufAssert assertThat(ByteBuf actual) { 29 | return new ByteBufAssert(actual); 30 | } 31 | 32 | private ByteBufAssert(ByteBuf actual) { 33 | super(actual, ByteBufAssert.class); 34 | } 35 | 36 | public ByteBufAssert matches(ByteBuf expected) { 37 | if (!ByteBufUtil.equals(actual, expected)) { 38 | failWithMessage( 39 | "Expected the buffer:\n%s\nbut got:\n%s", 40 | ByteBufUtil.prettyHexDump(expected), 41 | ByteBufUtil.prettyHexDump(actual)); 42 | } 43 | return this; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/EofResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.EnumSet; 22 | import java.util.Set; 23 | 24 | /** 25 | * 26 | */ 27 | public class EofResponse extends AbstractMySqlPacket implements MysqlServerPacket { 28 | 29 | private final int warnings; 30 | private final Set statusFlags = EnumSet.noneOf(ServerStatusFlag.class); 31 | 32 | public EofResponse(int sequenceId, int warnings, ServerStatusFlag... flags) { 33 | super(sequenceId); 34 | this.warnings = warnings; 35 | Collections.addAll(statusFlags, flags); 36 | } 37 | 38 | public EofResponse(int sequenceId, int warnings, Collection flags) { 39 | super(sequenceId); 40 | this.warnings = warnings; 41 | statusFlags.addAll(flags); 42 | } 43 | 44 | public int getWarnings() { 45 | return warnings; 46 | } 47 | 48 | public Set getStatusFlags() { 49 | return statusFlags; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/integration/client/HandshakeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql.integration.client; 18 | 19 | import com.github.mheath.netty.codec.mysql.Handshake; 20 | import com.github.mheath.netty.codec.mysql.HandshakeResponse; 21 | import com.github.mheath.netty.codec.mysql.Constants; 22 | import com.github.mheath.netty.codec.mysql.MysqlNativePasswordUtil; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static org.assertj.core.api.Assertions.assertThat; 26 | 27 | public class HandshakeTest extends AbstractIntegrationTest { 28 | 29 | @Test 30 | public void handshake() { 31 | try (Connection connection = client.connect()) { 32 | final Handshake handshake = (Handshake) connection.pollServer(); 33 | assertThat(handshake).isNotNull(); 34 | 35 | final HandshakeResponse response = HandshakeResponse 36 | .create() 37 | .addCapabilities(CLIENT_CAPABILITIES) 38 | .username(user) 39 | .addAuthData(MysqlNativePasswordUtil.hashPassword(password, handshake.getAuthPluginData())) 40 | .database(database) 41 | .authPluginName(Constants.MYSQL_NATIVE_PASSWORD) 42 | .build(); 43 | connection.write(response); 44 | connection.assertThatNextPacket().isOkResponse().hasAffectedRows(0); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /codec/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 4.0.0 21 | 22 | 23 | com.github.mheath 24 | netty-codec-mysql-parent 25 | 0.1-SNAPSHOT 26 | 27 | 28 | netty-codec-mysql 29 | 0.1-SNAPSHOT 30 | jar 31 | 32 | 33 | 5.1.43 34 | 35 | 36 | Netty MySQL Codec 37 | A Netty Codec for the MySQL protocol 38 | 39 | 40 | 41 | io.netty 42 | netty-codec 43 | 44 | 45 | org.junit.jupiter 46 | junit-jupiter-engine 47 | 48 | 49 | org.assertj 50 | assertj-core 51 | 52 | 53 | mysql 54 | mysql-connector-java 55 | ${mysql-connector.version} 56 | test 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/Command.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.Optional; 20 | 21 | /** 22 | * 23 | */ 24 | public enum Command { 25 | // Old (since MySQL 3.20) commands 26 | COM_SLEEP(0x00), 27 | COM_QUIT(0x01), 28 | COM_INIT_DB(0x02), 29 | COM_QUERY(0x03), 30 | COM_FIELD_LIST(0x04), 31 | COM_CREATE_DB(0x05), 32 | COM_DROP_DB(0x06), 33 | COM_REFRESH(0x07), 34 | COM_SHUTDOWN(0x08), 35 | COM_STATISTICS(0x09), 36 | COM_PROCESS_INFO(0x0a), 37 | COM_CONNECT(0x0b), 38 | COM_PROCESS_KILL(0x0c), 39 | COM_DEBUG(0x0d), 40 | COM_PING(0x0e), 41 | COM_TIME(0x0f), 42 | COM_DELAYED_INSERT(0x10), 43 | COM_CHANGE_USER(0x11), 44 | COM_RESET_CONNECTION(0x1f), 45 | COM_DAEMON(0x1d), 46 | 47 | // Prepared statements 48 | COM_STMT_PREPARE(0x16), 49 | COM_STMT_SEND_LONG_DATA(0x18), 50 | COM_STMT_EXECUTE(0x17), 51 | COM_STMT_CLOSE(0x19), 52 | COM_STMT_RESET(0x1a), 53 | 54 | // Stored procedures 55 | COM_SET_OPTION(0x1b), 56 | COM_STMT_FETCH(0x1c); 57 | 58 | // TODO Add replication protocol commands 59 | 60 | private final int commandCode; 61 | 62 | Command(int commandCode) { 63 | this.commandCode = commandCode; 64 | } 65 | 66 | public int getCommandCode() { 67 | return commandCode; 68 | } 69 | 70 | public static Optional findByCommandCode(int code) { 71 | for (Command command : values()) { 72 | if (command.getCommandCode() == code) { 73 | return Optional.of(command); 74 | } 75 | } 76 | return Optional.empty(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ColumnType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | /** 20 | * https://dev.mysql.com/doc/internals/en/com-query-response.html#column-type 21 | */ 22 | public enum ColumnType { 23 | MYSQL_TYPE_DECIMAL(0x00), 24 | MYSQL_TYPE_TINY(0x01), 25 | MYSQL_TYPE_SHORT(0x02), 26 | MYSQL_TYPE_LONG(0x03), 27 | MYSQL_TYPE_FLOAT(0x04), 28 | MYSQL_TYPE_DOUBLE(0x05), 29 | MYSQL_TYPE_NULL(0x06), 30 | MYSQL_TYPE_TIMESTAMP(0x07), 31 | MYSQL_TYPE_LONGLONG(0x08), 32 | MYSQL_TYPE_INT24(0x09), 33 | MYSQL_TYPE_DATE(0x0a), 34 | MYSQL_TYPE_TIME(0x0b), 35 | MYSQL_TYPE_DATETIME(0x0c), 36 | MYSQL_TYPE_YEAR(0x0d), 37 | MYSQL_TYPE_NEWDATE(0x0e), 38 | MYSQL_TYPE_VARCHAR(0x0f), 39 | MYSQL_TYPE_BIT(0x10), 40 | MYSQL_TYPE_TIMESTAMP2(0x11), 41 | MYSQL_TYPE_DATETIME2(0x12), 42 | MYSQL_TYPE_TIME2(0x13), 43 | MYSQL_TYPE_NEWDECIMAL(0xf6), 44 | MYSQL_TYPE_ENUM(0xf7), 45 | MYSQL_TYPE_SET(0xf8), 46 | MYSQL_TYPE_TINY_BLOB(0xf9), 47 | MYSQL_TYPE_MEDIUM_BLOB(0xfa), 48 | MYSQL_TYPE_LONG_BLOB(0xfb), 49 | MYSQL_TYPE_BLOB(0xfc), 50 | MYSQL_TYPE_VAR_STRING(0xfd), 51 | MYSQL_TYPE_STRING(0xfe), 52 | MYSQL_TYPE_GEOMETRY(0xff); 53 | 54 | private final int value; 55 | 56 | ColumnType(int value) { 57 | this.value = value; 58 | } 59 | 60 | public static ColumnType lookup(int value) { 61 | for (ColumnType columnType : values()) { 62 | if (columnType.value == value) { 63 | return columnType; 64 | } 65 | } 66 | return null; 67 | } 68 | 69 | public int getValue() { 70 | return value; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlNativePasswordUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | 21 | import java.security.MessageDigest; 22 | import java.security.NoSuchAlgorithmException; 23 | 24 | /** 25 | * Calculates a password hash for {@code mysql_native_password} authentication. 26 | */ 27 | public class MysqlNativePasswordUtil { 28 | 29 | public static byte[] hashPassword(String password, ByteBuf saltBuf) { 30 | byte[] salt = new byte[saltBuf.readableBytes()]; 31 | saltBuf.readBytes(salt); 32 | return hashPassword(password, salt); 33 | } 34 | 35 | /** 36 | * Calculates a hash of the user's password. 37 | * 38 | * @param password the user's password 39 | * @param salt the salt send from the server in the {@link Handshake} packet. 40 | * @return the hashed password 41 | */ 42 | public static byte[] hashPassword(String password, byte[] salt) { 43 | try { 44 | MessageDigest md = MessageDigest.getInstance("SHA-1"); 45 | 46 | byte[] hashedPassword = md.digest(password.getBytes()); 47 | 48 | md.reset(); 49 | byte[] doubleHashedPassword = md.digest(hashedPassword); 50 | 51 | md.reset(); 52 | md.update(salt, 0, 20); 53 | md.update(doubleHashedPassword); 54 | 55 | byte[] hash = md.digest(); 56 | for (int i = 0; i < hash.length; i++) { 57 | hash[i] = (byte) (hash[i] ^ hashedPassword[i]); 58 | } 59 | return hash; 60 | } catch (NoSuchAlgorithmException e) { 61 | throw new RuntimeException(e); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/MessageDecodeEncodeTester.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.embedded.EmbeddedChannel; 21 | import org.assertj.core.api.Assertions; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.function.Consumer; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | /** 30 | * 31 | */ 32 | class MessageDecodeEncodeTester { 33 | 34 | private EmbeddedChannel channel; 35 | private final List messages = new ArrayList(); 36 | private final List> assertions = new ArrayList<>(); 37 | private ByteBuf buf; 38 | 39 | public MessageDecodeEncodeTester pipeline(EmbeddedChannel channel) { 40 | this.channel = channel; 41 | return this; 42 | } 43 | 44 | public MessageDecodeEncodeTester addMessage(T message) { 45 | return addMessage(message, (m) -> assertThat(message.equals(m))); 46 | } 47 | 48 | public MessageDecodeEncodeTester addMessage(T message, Consumer assertion) { 49 | assertion.accept(message); 50 | messages.add(message); 51 | assertions.add(assertion); 52 | return this; 53 | } 54 | 55 | public MessageDecodeEncodeTester byteBuf(ByteBuf buf) { 56 | this.buf = buf; 57 | return this; 58 | } 59 | 60 | @SuppressWarnings("unchecked") 61 | public MessageDecodeEncodeTester assertDecode() { 62 | assertThat(channel.writeInbound(buf.copy())).isTrue(); 63 | for (Consumer assertion : assertions) { 64 | assertion.accept((T) channel.inboundMessages().poll()); 65 | } 66 | return this; 67 | } 68 | 69 | public MessageDecodeEncodeTester assertEncode() { 70 | for (Object msg : messages) { 71 | channel.writeOutbound(msg); 72 | } 73 | ByteBufAssert.assertThat(channel.readOutbound()).matches(buf); 74 | return this; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/integration/client/SimpleQueryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql.integration.client; 18 | 19 | import com.github.mheath.netty.codec.mysql.ColumnType; 20 | import com.github.mheath.netty.codec.mysql.MysqlPacketAssert; 21 | import org.junit.jupiter.api.Test; 22 | 23 | 24 | /** 25 | * 26 | */ 27 | public class SimpleQueryTest extends AbstractIntegrationTest { 28 | 29 | @Test 30 | public void simpleLongLongQuery() { 31 | assertSimpleQuery("SELECT 1 as test", "test", ColumnType.MYSQL_TYPE_LONGLONG).hasValues().first().isEqualTo("1"); 32 | } 33 | 34 | @Test 35 | public void simpleStringQuery() { 36 | assertSimpleQuery("SELECT 'Hello' as hello", "hello", ColumnType.MYSQL_TYPE_VAR_STRING).hasValues().first().isEqualTo("Hello"); 37 | } 38 | 39 | @Test 40 | public void simpleTimestampQuery() { 41 | assertSimpleQuery("SELECT now() as now", "now", ColumnType.MYSQL_TYPE_DATETIME).hasValues().first().isNotNull(); 42 | } 43 | 44 | private MysqlPacketAssert.ResultsetRowAssert assertSimpleQuery(String query, String alias, ColumnType type) { 45 | try (Connection connection = client.connect()) { 46 | connection.authenticate(); 47 | connection.query(query); 48 | 49 | connection.assertThatNextPacket() 50 | .isFieldCount() 51 | .hasFieldCount(1); 52 | 53 | connection.assertThatNextPacket() 54 | .isColumnDefinition() 55 | .hasColumnType(type) 56 | .hasName(alias); 57 | 58 | connection.assertThatNextPacket() 59 | .isEofResponse(); 60 | 61 | final MysqlPacketAssert.ResultsetRowAssert resultsetRow = connection.assertThatNextPacket().isResultsetRow(); 62 | 63 | connection.assertThatNextPacket() 64 | .isEofResponse(); 65 | 66 | return resultsetRow; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sql-parser/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | io.reactivesql 22 | netty-codec-mysql-parent 23 | 0.1-SNAPSHOT 24 | .. 25 | 26 | 27 | 4.0.0 28 | 29 | netty-codec-mysql 30 | 0.1-SNAPSHOT 31 | jar 32 | 33 | 34 | 5.1.43 35 | 36 | 37 | Netty MySQL Codec 38 | A Netty Codec for the MySQL protocol 39 | 40 | 41 | 42 | io.netty 43 | netty-codec 44 | 45 | 46 | junit 47 | junit 48 | 49 | 50 | org.assertj 51 | assertj-core 52 | 53 | 54 | mysql 55 | mysql-connector-java 56 | ${mysql-connector.version} 57 | test 58 | 59 | 60 | com.antlr.grammarsv4 61 | mysql 62 | 1.0-SNAPSHOT 63 | test 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.antlr 71 | antlr4-maven-plugin 72 | 4.7 73 | 74 | ${project.build.testSourceDirectory}/antlr4 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/CapabilityFlags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.channel.Channel; 20 | import io.netty.util.Attribute; 21 | import io.netty.util.AttributeKey; 22 | 23 | import java.util.EnumSet; 24 | import java.util.Set; 25 | 26 | /** 27 | * An enum of all the MySQL client/server capability flags. 28 | * 29 | * @see 30 | * Capability Flags Reference Documentation 31 | */ 32 | public enum CapabilityFlags { 33 | CLIENT_LONG_PASSWORD, 34 | CLIENT_FOUND_ROWS, 35 | CLIENT_LONG_FLAG, 36 | CLIENT_CONNECT_WITH_DB, 37 | CLIENT_NO_SCHEMA, 38 | CLIENT_COMPRESS, 39 | CLIENT_ODBC, 40 | CLIENT_LOCAL_FILES, 41 | CLIENT_IGNORE_SPACE, 42 | CLIENT_PROTOCOL_41, 43 | CLIENT_INTERACTIVE, 44 | CLIENT_SSL, 45 | CLIENT_IGNORE_SIGPIPE, 46 | CLIENT_TRANSACTIONS, 47 | CLIENT_RESERVED, 48 | CLIENT_SECURE_CONNECTION, 49 | CLIENT_MULTI_STATEMENTS, 50 | CLIENT_MULTI_RESULTS, 51 | CLIENT_PS_MULTI_RESULTS, 52 | CLIENT_PLUGIN_AUTH, 53 | CLIENT_CONNECT_ATTRS, 54 | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA, 55 | CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS, 56 | CLIENT_SESSION_TRACK, 57 | CLIENT_DEPRECATE_EOF, 58 | UNKNOWN_25, 59 | UNKNOWN_26, 60 | UNKNOWN_27, 61 | UNKNOWN_28, 62 | UNKNOWN_29, 63 | UNKNOWN_30, 64 | UNKNOWN_31; 65 | 66 | public static EnumSet getImplicitCapabilities() { 67 | return EnumSet.of( 68 | CapabilityFlags.CLIENT_LONG_PASSWORD, 69 | CapabilityFlags.CLIENT_PROTOCOL_41, 70 | CapabilityFlags.CLIENT_TRANSACTIONS, 71 | CapabilityFlags.CLIENT_SECURE_CONNECTION 72 | ); 73 | 74 | } 75 | 76 | private static final AttributeKey> capabilitiesKey = AttributeKey.newInstance(CapabilityFlags.class.getName()); 77 | 78 | public static EnumSet getCapabilitiesAttr(Channel channel) { 79 | final Attribute> attr = channel.attr(capabilitiesKey); 80 | if (attr.get() == null) { 81 | attr.set(getImplicitCapabilities()); 82 | } 83 | return attr.get(); 84 | } 85 | 86 | public static void setCapabilitiesAttr(Channel channel, Set capabilities) { 87 | final Attribute> attr = channel.attr(capabilitiesKey); 88 | attr.set(EnumSet.copyOf(capabilities)); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | 7 | com.github.mheath 8 | netty-codec-mysql-parent 9 | 0.1-SNAPSHOT 10 | pom 11 | 12 | Netty MySQL Codec Parent 13 | A Netty codec for the MySQL/MariaDB protocol 14 | https://github.com/mheath/netty-codec-mysql 15 | 16 | 17 | 1.8 18 | 5.1.0 19 | 4.1.3.Final 20 | 3.5.2 21 | 22 | 23 | 24 | codec 25 | examples 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-compiler-plugin 34 | 35 | ${java.version} 36 | ${java.version} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | com.github.mheath 47 | netty-codec-mysql 48 | ${project.version} 49 | 50 | 51 | io.netty 52 | netty-codec 53 | ${netty.version} 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter-engine 58 | ${junit.version} 59 | test 60 | 61 | 62 | org.hamcrest 63 | hamcrest-library 64 | 1.3 65 | test 66 | 67 | 68 | org.assertj 69 | assertj-core 70 | ${assertj.version} 71 | test 72 | 73 | 74 | 75 | 76 | 77 | 78 | The Apache Software License, Version 2.0 79 | http://www.apache.org/licenses/LICENSE-2.0.txt 80 | repo 81 | 82 | 83 | 84 | 85 | scm:git:https://github.com/cloudfoundry-community/java-nats.git 86 | scm:git:https://github.com/cloudfoundry-community/java-nats 87 | scm:git:https://github.com/cloudfoundry-community/java-nats 88 | 89 | 90 | 91 | 92 | Mike Heath 93 | mheath@pivotal.io 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlClientConnectionPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.EnumSet; 5 | import java.util.List; 6 | 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.codec.DecoderException; 10 | 11 | /** 12 | * 13 | */ 14 | public class MysqlClientConnectionPacketDecoder extends AbstractPacketDecoder implements MysqlClientPacketDecoder { 15 | 16 | public MysqlClientConnectionPacketDecoder() { 17 | this(DEFAULT_MAX_PACKET_SIZE); 18 | } 19 | 20 | public MysqlClientConnectionPacketDecoder(int maxPacketSize) { 21 | super(maxPacketSize); 22 | } 23 | 24 | @Override 25 | protected void decodePacket(ChannelHandlerContext ctx, int sequenceId, ByteBuf packet, List out) { 26 | final EnumSet clientCapabilities = CodecUtils.readIntEnumSet(packet, CapabilityFlags.class); 27 | 28 | if (!clientCapabilities.contains(CapabilityFlags.CLIENT_PROTOCOL_41)) { 29 | throw new DecoderException("MySQL client protocol 4.1 support required"); 30 | } 31 | 32 | final HandshakeResponse.Builder response = HandshakeResponse.create(); 33 | response.addCapabilities(clientCapabilities) 34 | .maxPacketSize((int)packet.readUnsignedIntLE()); 35 | final MysqlCharacterSet characterSet = MysqlCharacterSet.findById(packet.readByte()); 36 | response.characterSet(characterSet); 37 | packet.skipBytes(23); 38 | if (packet.isReadable()) { 39 | response.username(CodecUtils.readNullTerminatedString(packet, characterSet.getCharset())); 40 | 41 | final EnumSet serverCapabilities = CapabilityFlags.getCapabilitiesAttr(ctx.channel()); 42 | final EnumSet capabilities = EnumSet.copyOf(clientCapabilities); 43 | capabilities.retainAll(serverCapabilities); 44 | 45 | final int authResponseLength; 46 | if (capabilities.contains(CapabilityFlags.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)) { 47 | authResponseLength = (int)CodecUtils.readLengthEncodedInteger(packet); 48 | } else if (capabilities.contains(CapabilityFlags.CLIENT_SECURE_CONNECTION)) { 49 | authResponseLength = packet.readUnsignedByte(); 50 | } else { 51 | authResponseLength = CodecUtils.findNullTermLen(packet); 52 | } 53 | response.addAuthData(packet, authResponseLength); 54 | 55 | if (capabilities.contains(CapabilityFlags.CLIENT_CONNECT_WITH_DB)) { 56 | response.database(CodecUtils.readNullTerminatedString(packet, characterSet.getCharset())); 57 | } 58 | 59 | if (capabilities.contains(CapabilityFlags.CLIENT_PLUGIN_AUTH)) { 60 | response.authPluginName(CodecUtils.readNullTerminatedString(packet, StandardCharsets.UTF_8)); 61 | } 62 | 63 | if (capabilities.contains(CapabilityFlags.CLIENT_CONNECT_ATTRS)) { 64 | final long keyValueLen = CodecUtils.readLengthEncodedInteger(packet); 65 | for (int i = 0; i < keyValueLen; i++) { 66 | response.addAttribute( 67 | CodecUtils.readLengthEncodedString(packet, StandardCharsets.UTF_8), 68 | CodecUtils.readLengthEncodedString(packet, StandardCharsets.UTF_8)); 69 | } 70 | } 71 | } 72 | out.add(response.build()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/AbstractPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | import io.netty.handler.codec.TooLongFrameException; 7 | 8 | import java.nio.charset.Charset; 9 | import java.util.EnumSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * 15 | */ 16 | public abstract class AbstractPacketDecoder extends ByteToMessageDecoder implements Constants { 17 | 18 | private final int maxPacketSize; 19 | 20 | public AbstractPacketDecoder(int maxPacketSize) { 21 | this.maxPacketSize = maxPacketSize; 22 | } 23 | 24 | @Override 25 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 26 | if (in.isReadable(4)) { 27 | in.markReaderIndex(); 28 | final int packetSize = in.readUnsignedMediumLE(); 29 | if (packetSize > maxPacketSize) { 30 | throw new TooLongFrameException("Received a packet of size " + packetSize + " but the maximum packet size is " + maxPacketSize); 31 | } 32 | final int sequenceId = in.readByte(); 33 | if (!in.isReadable(packetSize)) { 34 | in.resetReaderIndex(); 35 | return; 36 | } 37 | final ByteBuf packet = in.readSlice(packetSize); 38 | 39 | decodePacket(ctx, sequenceId, packet, out); 40 | } 41 | } 42 | 43 | protected abstract void decodePacket(ChannelHandlerContext ctx, int sequenceId, ByteBuf packet, List out); 44 | 45 | protected OkResponse decodeOkResponse(int sequenceId, ByteBuf packet, Set capabilities, 46 | Charset charset) { 47 | 48 | final OkResponse.Builder builder = OkResponse.builder() 49 | .sequenceId(sequenceId) 50 | .affectedRows(CodecUtils.readLengthEncodedInteger(packet)) 51 | .lastInsertId(CodecUtils.readLengthEncodedInteger(packet)); 52 | 53 | final EnumSet statusFlags = CodecUtils.readShortEnumSet(packet, ServerStatusFlag.class); 54 | if (capabilities.contains(CapabilityFlags.CLIENT_PROTOCOL_41)) { 55 | builder 56 | .addStatusFlags(statusFlags) 57 | .warnings(packet.readUnsignedShortLE()); 58 | } else if (capabilities.contains(CapabilityFlags.CLIENT_TRANSACTIONS)) { 59 | builder.addStatusFlags(statusFlags); 60 | } 61 | 62 | if (capabilities.contains(CapabilityFlags.CLIENT_SESSION_TRACK)) { 63 | builder.info(CodecUtils.readLengthEncodedString(packet, charset)); 64 | if (statusFlags.contains(ServerStatusFlag.SESSION_STATE_CHANGED)) { 65 | builder.sessionStateChanges(CodecUtils.readLengthEncodedString(packet, charset)); 66 | } 67 | } else { 68 | builder.info(CodecUtils.readFixedLengthString(packet, packet.readableBytes(), charset)); 69 | } 70 | return builder.build(); 71 | } 72 | 73 | protected EofResponse decodeEofResponse(int sequenceId, ByteBuf packet, Set capabilities) { 74 | if (capabilities.contains(CapabilityFlags.CLIENT_PROTOCOL_41)) { 75 | return new EofResponse( 76 | sequenceId, 77 | packet.readUnsignedShortLE(), 78 | CodecUtils.readShortEnumSet(packet, ServerStatusFlag.class)); 79 | } else { 80 | return new EofResponse(sequenceId, 0); 81 | } 82 | } 83 | 84 | protected ErrorResponse decodeErrorResponse(int sequenceId, ByteBuf packet, Charset charset) { 85 | final int errorNumber = packet.readUnsignedShortLE(); 86 | 87 | final byte[] sqlState; 88 | sqlState = new byte[SQL_STATE_SIZE]; 89 | packet.readBytes(sqlState); 90 | 91 | final String message = CodecUtils.readFixedLengthString(packet, packet.readableBytes(), charset); 92 | 93 | return new ErrorResponse(sequenceId, errorNumber, sqlState, message); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlClientPacketEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | 22 | import java.nio.charset.Charset; 23 | import java.util.Set; 24 | 25 | /** 26 | * 27 | */ 28 | public class MysqlClientPacketEncoder extends AbstractPacketEncoder { 29 | 30 | @Override 31 | protected void encodePacket(ChannelHandlerContext ctx, MysqlClientPacket packet, ByteBuf buf) throws Exception { 32 | final Charset charset = MysqlCharacterSet.getClientCharsetAttr(ctx.channel()).getCharset(); 33 | final Set capabilities = CapabilityFlags.getCapabilitiesAttr(ctx.channel()); 34 | if (packet instanceof CommandPacket) { 35 | encodeCommandPacket((CommandPacket) packet, buf, charset); 36 | } else if (packet instanceof HandshakeResponse) { 37 | final HandshakeResponse handshakeResponse = (HandshakeResponse) packet; 38 | encodeHandshakeResponse(handshakeResponse, buf, charset, capabilities); 39 | } else { 40 | throw new IllegalStateException("Unknown client packet type: " + packet.getClass()); 41 | } 42 | } 43 | 44 | private void encodeCommandPacket(CommandPacket packet, ByteBuf buf, Charset charset) { 45 | buf.writeByte(packet.getCommand().getCommandCode()); 46 | if (packet instanceof QueryCommand) { 47 | buf.writeCharSequence(((QueryCommand) packet).getQuery(), charset); 48 | } 49 | } 50 | 51 | private void encodeHandshakeResponse(HandshakeResponse handshakeResponse, ByteBuf buf, Charset charset, Set capabilities) { 52 | buf.writeIntLE((int) CodecUtils.toLong(handshakeResponse.getCapabilityFlags())) 53 | .writeIntLE(handshakeResponse.getMaxPacketSize()) 54 | .writeByte(handshakeResponse.getCharacterSet().getId()) 55 | .writeZero(23); 56 | 57 | CodecUtils.writeNullTerminatedString(buf, handshakeResponse.getUsername(), charset); 58 | 59 | if (capabilities.contains(CapabilityFlags.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)) { 60 | CodecUtils.writeLengthEncodedInt(buf, (long) handshakeResponse.getAuthPluginData().writableBytes()); 61 | buf.writeBytes(handshakeResponse.getAuthPluginData()); 62 | } else if (capabilities.contains(CapabilityFlags.CLIENT_SECURE_CONNECTION)) { 63 | buf.writeByte(handshakeResponse.getAuthPluginData().readableBytes()); 64 | buf.writeBytes(handshakeResponse.getAuthPluginData()); 65 | } else { 66 | buf.writeBytes(handshakeResponse.getAuthPluginData()); 67 | buf.writeByte(0x00); 68 | } 69 | 70 | if (capabilities.contains(CapabilityFlags.CLIENT_CONNECT_WITH_DB)) { 71 | CodecUtils.writeNullTerminatedString(buf, handshakeResponse.getDatabase(), charset); 72 | } 73 | 74 | if (capabilities.contains(CapabilityFlags.CLIENT_PLUGIN_AUTH)) { 75 | CodecUtils.writeNullTerminatedString(buf, handshakeResponse.getAuthPluginName(), charset); 76 | } 77 | if (capabilities.contains(CapabilityFlags.CLIENT_CONNECT_ATTRS)) { 78 | CodecUtils.writeLengthEncodedInt(buf, (long) handshakeResponse.getAttributes().size()); 79 | handshakeResponse.getAttributes().forEach((key, value) -> { 80 | CodecUtils.writeLengthEncodedString(buf, key, charset); 81 | CodecUtils.writeLengthEncodedString(buf, value, charset); 82 | }); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/OkResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.EnumSet; 22 | import java.util.Set; 23 | 24 | /** 25 | * 26 | */ 27 | public class OkResponse extends AbstractMySqlPacket implements MysqlServerPacket { 28 | 29 | private final long affectedRows; 30 | private final long lastInsertId; 31 | 32 | private final int warnings; 33 | private final String info; 34 | 35 | private final Set statusFlags = EnumSet.noneOf(ServerStatusFlag.class); 36 | private final String sessionStateChanges; 37 | 38 | 39 | public OkResponse(Builder builder) { 40 | super(builder.sequenceId); 41 | affectedRows = builder.affectedRows; 42 | lastInsertId = builder.lastInsertId; 43 | 44 | warnings = builder.warnings; 45 | info = builder.info; 46 | 47 | statusFlags.addAll(builder.statusFlags); 48 | sessionStateChanges = builder.sessionStateChanges; 49 | } 50 | 51 | public long getAffectedRows() { 52 | return affectedRows; 53 | } 54 | 55 | public long getLastInsertId() { 56 | return lastInsertId; 57 | } 58 | 59 | public int getWarnings() { 60 | return warnings; 61 | } 62 | 63 | public String getInfo() { 64 | return info; 65 | } 66 | 67 | public Set getStatusFlags() { 68 | return EnumSet.copyOf(statusFlags); 69 | } 70 | 71 | public String getSessionStateChanges() { 72 | return sessionStateChanges; 73 | } 74 | 75 | public static Builder builder() { 76 | return new Builder(); 77 | } 78 | 79 | 80 | public static class Builder { 81 | private int sequenceId; 82 | 83 | private long affectedRows; 84 | private long lastInsertId; 85 | 86 | private int warnings; 87 | private String info; 88 | 89 | private Set statusFlags = EnumSet.noneOf(ServerStatusFlag.class); 90 | private String sessionStateChanges; 91 | 92 | public Builder sequenceId(int sequenceId) { 93 | this.sequenceId = sequenceId; 94 | return this; 95 | } 96 | 97 | public Builder affectedRows(long affectedRows) { 98 | this.affectedRows = affectedRows; 99 | return this; 100 | } 101 | 102 | public Builder lastInsertId(long lastInsertId) { 103 | this.lastInsertId = lastInsertId; 104 | return this; 105 | } 106 | 107 | public Builder addStatusFlags(ServerStatusFlag statusFlag, ServerStatusFlag... statusFlags) { 108 | this.statusFlags.add(statusFlag); 109 | Collections.addAll(this.statusFlags, statusFlags); 110 | return this; 111 | } 112 | 113 | public Builder addStatusFlags(Collection statusFlags) { 114 | this.statusFlags.addAll(statusFlags); 115 | return this; 116 | } 117 | 118 | public Builder warnings(int warnings) { 119 | this.warnings = warnings; 120 | return this; 121 | } 122 | 123 | public Builder info(String info) { 124 | this.info = info; 125 | return this; 126 | } 127 | 128 | public Builder sessionStateChanges(String sessionStateChanges) { 129 | this.sessionStateChanges = sessionStateChanges; 130 | return this; 131 | } 132 | 133 | public OkResponse build() { 134 | return new OkResponse(this); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/CodecUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.Unpooled; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.Arrays; 24 | import java.util.EnumSet; 25 | import java.util.Set; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | 30 | /** 31 | * 32 | */ 33 | public class CodecUtilsTest { 34 | 35 | @Test 36 | public void decodeLengthEncodedInteger() { 37 | assertThat(readLengthEncodedBytes((byte) 0)).isEqualTo(0); 38 | assertThat(readLengthEncodedBytes((byte) 1)).isEqualTo(1); 39 | assertThat(readLengthEncodedBytes((byte) 2)).isEqualTo(2); 40 | assertThat(readLengthEncodedBytes((byte) 0xf9)).isEqualTo(0xf9); 41 | assertThat(readLengthEncodedBytes((byte) 0xfa)).isEqualTo(0xfa); 42 | assertThat(readLengthEncodedBytes((byte) 0xfb)).isEqualTo(-1); // NUL value 43 | assertThat(readLengthEncodedBytes((byte) 0xfc, (byte) 0xfb, (byte) 0x00)).isEqualTo(0xfb); 44 | 45 | } 46 | 47 | @Test 48 | public void encodeLengthEncodedInteger() { 49 | assertLEI(0l, TestUtils.toBuf(0)); 50 | assertLEI(1l, TestUtils.toBuf(1)); 51 | assertLEI(null, TestUtils.toBuf(0xfb)); 52 | assertLEI(0xfcl, TestUtils.toBuf(0xfc, 0xfc, 0x00)); 53 | assertLEI(0x10000l, TestUtils.toBuf(0xfd, 0x00, 0x00, 0x01)); 54 | assertLEI(0x1000000l, TestUtils.toBuf(0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00)); 55 | } 56 | 57 | private void assertLEI(Long value, ByteBuf expected) { 58 | final ByteBuf buf = Unpooled.buffer(); 59 | try { 60 | CodecUtils.writeLengthEncodedInt(buf, value); 61 | ByteBufAssert.assertThat(buf).matches(expected); 62 | } finally { 63 | buf.release(); 64 | } 65 | } 66 | 67 | private long readLengthEncodedBytes(byte... bytes) { 68 | return CodecUtils.readLengthEncodedInteger(Unpooled.wrappedBuffer(bytes)); 69 | } 70 | 71 | @Test 72 | public void enumSetVectorConversion() { 73 | assertEnumSetVectorConversion( 74 | CapabilityFlags.class, 75 | CapabilityFlags.CLIENT_SSL, 76 | CapabilityFlags.CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS, 77 | CapabilityFlags.CLIENT_INTERACTIVE); 78 | assertEnumSetVectorConversion( 79 | ColumnFlag.class, 80 | ColumnFlag.BINARY, 81 | ColumnFlag.PRI_KEY, 82 | ColumnFlag.UNIQUE_KEY, 83 | ColumnFlag.SET); 84 | assertEnumSetVectorConversion(ServerStatusFlag.class); 85 | assertEnumSetVectorConversion(CapabilityFlags.class, CapabilityFlags.values()); 86 | } 87 | 88 | public > void assertEnumSetVectorConversion(Class type, T... enums) { 89 | final Set set; 90 | if (enums.length == 0) { 91 | set = EnumSet.noneOf(type); 92 | } else { 93 | set = EnumSet.copyOf(Arrays.asList(enums)); 94 | } 95 | final long vector = CodecUtils.toLong(set); 96 | final Set decodedSet = CodecUtils.toEnumSet(type, vector); 97 | assertThat(decodedSet).contains(enums); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlServerConnectionPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.CodecException; 7 | import io.netty.util.CharsetUtil; 8 | 9 | import java.nio.charset.Charset; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * 15 | */ 16 | public class MysqlServerConnectionPacketDecoder extends AbstractPacketDecoder implements MysqlServerPacketDecoder { 17 | 18 | public MysqlServerConnectionPacketDecoder() { 19 | this(DEFAULT_MAX_PACKET_SIZE); 20 | } 21 | 22 | public MysqlServerConnectionPacketDecoder(int maxPacketSize) { 23 | super(maxPacketSize); 24 | } 25 | 26 | @Override 27 | protected void decodePacket(ChannelHandlerContext ctx, int sequenceId, ByteBuf packet, List out) { 28 | final Channel channel = ctx.channel(); 29 | final Set capabilities = CapabilityFlags.getCapabilitiesAttr(channel); 30 | final Charset serverCharset = MysqlCharacterSet.getServerCharsetAttr(channel).getCharset(); 31 | 32 | final int header = packet.readByte() & 0xff; 33 | switch (header) { 34 | case RESPONSE_OK: 35 | out.add(decodeOkResponse(sequenceId, packet, capabilities, serverCharset)); 36 | break; 37 | case RESPONSE_EOF: 38 | if (capabilities.contains(CapabilityFlags.CLIENT_PLUGIN_AUTH)) { 39 | decodeAuthSwitchRequest(sequenceId, packet, out); 40 | } else { 41 | out.add(decodeEofResponse(sequenceId, packet, capabilities)); 42 | } 43 | break; 44 | case RESPONSE_ERROR: 45 | out.add(decodeErrorResponse(sequenceId, packet, serverCharset)); 46 | break; 47 | case 1: 48 | // TODO Decode auth more data packet: https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthMoreData 49 | throw new UnsupportedOperationException("Implement auth more data"); 50 | default: 51 | decodeHandshake(packet, out, header); 52 | } 53 | } 54 | 55 | private void decodeAuthSwitchRequest(int sequenceId, ByteBuf packet, List out) { 56 | // TODO Implement AuthSwitchRequest decode 57 | throw new UnsupportedOperationException("Implement decodeAuthSwitchRequest decode."); 58 | } 59 | 60 | private void decodeHandshake(ByteBuf packet, List out, int protocolVersion) { 61 | if (protocolVersion < MINIMUM_SUPPORTED_PROTOCOL_VERSION) { 62 | throw new CodecException("Unsupported version of MySQL"); 63 | } 64 | 65 | final Handshake.Builder builder = Handshake.builder(); 66 | builder 67 | .protocolVersion(protocolVersion) 68 | .serverVersion(CodecUtils.readNullTerminatedString(packet)) 69 | .connectionId(packet.readIntLE()) 70 | .addAuthData(packet, Constants.AUTH_PLUGIN_DATA_PART1_LEN); 71 | 72 | packet.skipBytes(1); // Skip auth plugin data terminator 73 | builder.addCapabilities(CodecUtils.toEnumSet(CapabilityFlags.class, packet.readUnsignedShortLE())); 74 | if (packet.isReadable()) { 75 | builder 76 | .characterSet(MysqlCharacterSet.findById(packet.readByte())) 77 | .addServerStatus(CodecUtils.readShortEnumSet(packet, ServerStatusFlag.class)) 78 | .addCapabilities( 79 | CodecUtils.toEnumSet(CapabilityFlags.class, packet.readUnsignedShortLE() << Short.SIZE)); 80 | if (builder.hasCapability(CapabilityFlags.CLIENT_SECURE_CONNECTION)) { 81 | final int authDataLen = packet.readByte(); 82 | 83 | packet.skipBytes(Constants.HANDSHAKE_RESERVED_BYTES); // Skip reserved bytes 84 | final int readableBytes = 85 | Math.max(Constants.AUTH_PLUGIN_DATA_PART2_MIN_LEN, 86 | authDataLen - Constants.AUTH_PLUGIN_DATA_PART1_LEN); 87 | builder.addAuthData(packet, readableBytes); 88 | if (builder.hasCapability(CapabilityFlags.CLIENT_PLUGIN_AUTH) && packet.isReadable()) { 89 | int len = packet.readableBytes(); 90 | if (packet.getByte(packet.readerIndex() + len - 1) == 0) { 91 | len--; 92 | } 93 | builder.authPluginName(CodecUtils.readFixedLengthString(packet, len, CharsetUtil.UTF_8)); 94 | packet.skipBytes(1); 95 | } 96 | } 97 | } 98 | final Handshake handshake = builder.build(); 99 | out.add(handshake); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/HandshakeResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.DefaultByteBufHolder; 21 | 22 | import java.util.EnumSet; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.Objects; 26 | import java.util.Set; 27 | 28 | /** 29 | * 30 | */ 31 | public class HandshakeResponse extends DefaultByteBufHolder implements MysqlClientPacket { 32 | 33 | private final Set capabilityFlags = EnumSet.noneOf(CapabilityFlags.class); 34 | private final int maxPacketSize; 35 | private final MysqlCharacterSet characterSet; 36 | private final String username; 37 | private final String database; 38 | private final String authPluginName; 39 | private final Map attributes = new HashMap(); 40 | 41 | private HandshakeResponse(Builder builder) { 42 | super(builder.authPluginData); 43 | this.capabilityFlags.addAll(builder.capabilities); 44 | this.maxPacketSize = builder.maxPacketSize; 45 | this.characterSet = builder.characterSet; 46 | this.username = builder.username; 47 | this.database = builder.database; 48 | this.authPluginName = builder.authPluginName; 49 | this.attributes.putAll(builder.attributes); 50 | } 51 | 52 | public static Builder create() { 53 | return new Builder(); 54 | } 55 | 56 | public static HandshakeResponse createSslResponse(Set capabilities, int maxPacketSize, 57 | MysqlCharacterSet characterSet) { 58 | return create() 59 | .maxPacketSize(maxPacketSize) 60 | .characterSet(characterSet) 61 | .addCapabilities(capabilities) 62 | .addCapabilities(CapabilityFlags.CLIENT_SSL) 63 | .build(); 64 | } 65 | 66 | public ByteBuf getAuthPluginData() { 67 | return content(); 68 | } 69 | 70 | public Set getCapabilityFlags() { 71 | return EnumSet.copyOf(capabilityFlags); 72 | } 73 | 74 | public int getMaxPacketSize() { 75 | return maxPacketSize; 76 | } 77 | 78 | public MysqlCharacterSet getCharacterSet() { 79 | return characterSet; 80 | } 81 | 82 | public String getUsername() { 83 | return username; 84 | } 85 | 86 | public String getDatabase() { 87 | return database; 88 | } 89 | 90 | public String getAuthPluginName() { 91 | return authPluginName; 92 | } 93 | 94 | public Map getAttributes() { 95 | return attributes; 96 | } 97 | 98 | @Override 99 | public int getSequenceId() { 100 | return 1; 101 | } 102 | 103 | public static class Builder extends AbstractAuthPluginDataBuilder { 104 | private int maxPacketSize = Constants.DEFAULT_MAX_PACKET_SIZE; 105 | private MysqlCharacterSet characterSet = MysqlCharacterSet.DEFAULT; 106 | private String username; 107 | private String database; 108 | private String authPluginName; 109 | private Map attributes = new HashMap(); 110 | 111 | public Builder maxPacketSize(int maxPacketSize) { 112 | this.maxPacketSize = maxPacketSize; 113 | return this; 114 | } 115 | 116 | public Builder characterSet(MysqlCharacterSet characterSet) { 117 | Objects.requireNonNull(characterSet, "characterSet can NOT be null"); 118 | this.characterSet = characterSet; 119 | return this; 120 | } 121 | 122 | public Builder username(String username) { 123 | this.username = username; 124 | return this; 125 | } 126 | 127 | public Builder database(String database) { 128 | addCapabilities(CapabilityFlags.CLIENT_CONNECT_WITH_DB); 129 | this.database = database; 130 | return this; 131 | } 132 | 133 | public Builder authPluginName(String authPluginName) { 134 | addCapabilities(CapabilityFlags.CLIENT_PLUGIN_AUTH); 135 | this.authPluginName = authPluginName; 136 | return this; 137 | } 138 | 139 | public Builder addAttribute(String key, String value) { 140 | attributes.put(key, value); 141 | return this; 142 | } 143 | 144 | public HandshakeResponse build() { 145 | return new HandshakeResponse(this); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/ColumnDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.EnumSet; 22 | import java.util.Set; 23 | 24 | /** 25 | * 26 | */ 27 | public class ColumnDefinition extends AbstractMySqlPacket implements MysqlServerPacket { 28 | private final String catalog; 29 | private final String schema; 30 | private final String table; 31 | private final String orgTable; 32 | private final String name; 33 | private final String orgName; 34 | private final MysqlCharacterSet characterSet; 35 | private final long columnLength; 36 | private final ColumnType type; 37 | private final Set flags = EnumSet.noneOf(ColumnFlag.class); 38 | private final int decimals; 39 | 40 | private ColumnDefinition(Builder builder) { 41 | super(builder.sequenceId); 42 | this.catalog = builder.catalog; 43 | this.schema = builder.schema; 44 | this.table = builder.table; 45 | this.orgTable = builder.orgTable; 46 | this.name = builder.name; 47 | this.orgName = builder.orgName; 48 | this.characterSet = builder.characterSet; 49 | this.columnLength = builder.columnLength; 50 | this.type = builder.type; 51 | this.flags.addAll(builder.flags); 52 | this.decimals = builder.decimals; 53 | } 54 | 55 | public static Builder builder() { 56 | return new Builder(); 57 | } 58 | 59 | public String getCatalog() { 60 | return catalog; 61 | } 62 | 63 | public String getSchema() { 64 | return schema; 65 | } 66 | 67 | public String getTable() { 68 | return table; 69 | } 70 | 71 | public String getOrgTable() { 72 | return orgTable; 73 | } 74 | 75 | public String getName() { 76 | return name; 77 | } 78 | 79 | public String getOrgName() { 80 | return orgName; 81 | } 82 | 83 | public MysqlCharacterSet getCharacterSet() { 84 | return characterSet; 85 | } 86 | 87 | public long getColumnLength() { 88 | return columnLength; 89 | } 90 | 91 | public ColumnType getType() { 92 | return type; 93 | } 94 | 95 | public Set getFlags() { 96 | return flags; 97 | } 98 | 99 | public int getDecimals() { 100 | return decimals; 101 | } 102 | 103 | public static class Builder { 104 | private final Set flags = EnumSet.noneOf(ColumnFlag.class); 105 | private int sequenceId; 106 | private String catalog = "def"; 107 | private String schema; 108 | private String table; 109 | private String orgTable; 110 | private String name; 111 | private String orgName; 112 | private MysqlCharacterSet characterSet = MysqlCharacterSet.UTF8_GENERAL_CI; 113 | private long columnLength; 114 | private ColumnType type; 115 | private int decimals; 116 | 117 | public Builder sequenceId(int sequenceId) { 118 | this.sequenceId = sequenceId; 119 | return this; 120 | } 121 | 122 | public Builder catalog(String catalog) { 123 | this.catalog = catalog; 124 | return this; 125 | } 126 | 127 | public Builder schema(String schema) { 128 | this.schema = schema; 129 | return this; 130 | } 131 | 132 | public Builder table(String table) { 133 | this.table = table; 134 | return this; 135 | } 136 | 137 | public Builder orgTable(String orgTable) { 138 | this.orgTable = orgTable; 139 | return this; 140 | } 141 | 142 | public Builder name(String name) { 143 | this.name = name; 144 | return this; 145 | } 146 | 147 | public Builder orgName(String orgName) { 148 | this.orgName = orgName; 149 | return this; 150 | } 151 | 152 | public Builder characterSet(MysqlCharacterSet characterSet) { 153 | this.characterSet = characterSet; 154 | return this; 155 | } 156 | 157 | public Builder columnLength(long columnLength) { 158 | this.columnLength = columnLength; 159 | return this; 160 | } 161 | 162 | public Builder type(ColumnType type) { 163 | this.type = type; 164 | return this; 165 | } 166 | 167 | public Builder addFlags(ColumnFlag... flags) { 168 | Collections.addAll(this.flags, flags); 169 | return this; 170 | } 171 | 172 | public Builder addFlags(Collection flags) { 173 | flags.addAll(flags); 174 | return this; 175 | } 176 | 177 | public Builder decimals(int decimals) { 178 | this.decimals = decimals; 179 | return this; 180 | } 181 | 182 | public ColumnDefinition build() { 183 | return new ColumnDefinition(this); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/MysqlPacketAssert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import org.assertj.core.api.AbstractAssert; 20 | import org.assertj.core.api.AbstractListAssert; 21 | import org.assertj.core.api.Assertions; 22 | import org.assertj.core.api.ObjectAssert; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * 28 | */ 29 | public class MysqlPacketAssert extends AbstractAssert { 30 | 31 | public MysqlPacketAssert(MysqlPacket actual) { 32 | super(actual, MysqlPacketAssert.class); 33 | } 34 | 35 | public static MysqlPacketAssert assertThat(MysqlPacket actual) { 36 | return new MysqlPacketAssert(actual); 37 | } 38 | 39 | public ColumnDefinitionAssert isColumnDefinition() { 40 | isNotNull(); 41 | isInstanceOf(ColumnDefinition.class); 42 | return new ColumnDefinitionAssert((ColumnDefinition) actual); 43 | } 44 | 45 | public static class ColumnDefinitionAssert extends AbstractAssert { 46 | public ColumnDefinitionAssert(ColumnDefinition actual) { 47 | super(actual, ColumnDefinitionAssert.class); 48 | } 49 | 50 | public ColumnDefinitionAssert hasColumnType(ColumnType type) { 51 | isNotNull(); 52 | Assertions.assertThat(actual.getType()).isEqualTo(type); 53 | return this; 54 | } 55 | 56 | public ColumnDefinitionAssert hasName(String name) { 57 | isNotNull(); 58 | Assertions.assertThat(actual.getName()).isEqualTo(name); 59 | return this; 60 | } 61 | } 62 | 63 | public EofResponseAssert isEofResponse() { 64 | isNotNull(); 65 | isInstanceOf(EofResponse.class); 66 | return new EofResponseAssert((EofResponse)actual); 67 | } 68 | 69 | public static class EofResponseAssert extends AbstractAssert { 70 | public EofResponseAssert(EofResponse actual) { 71 | super(actual, EofResponseAssert.class); 72 | } 73 | } 74 | 75 | public FieldCountAssert isFieldCount() { 76 | isNotNull(); 77 | isInstanceOf(ColumnCount.class); 78 | return FieldCountAssert.assertThat((ColumnCount) actual); 79 | } 80 | 81 | public static class FieldCountAssert extends AbstractAssert { 82 | public FieldCountAssert(ColumnCount actual) { 83 | super(actual, FieldCountAssert.class); 84 | } 85 | 86 | public static FieldCountAssert assertThat(ColumnCount actual) { 87 | return new FieldCountAssert(actual); 88 | } 89 | 90 | public FieldCountAssert hasFieldCount(int count) { 91 | isNotNull(); 92 | if (count != actual.getFieldCount()) { 93 | failWithMessage("Expected a field count of <%i> but is <%i>", count, actual.getFieldCount()); 94 | } 95 | return this; 96 | } 97 | } 98 | public OkResponseAssert isOkResponse() { 99 | isNotNull(); 100 | if (actual instanceof ErrorResponse) { 101 | throw new AssertionError("Expected OkResponse but got ErrorResponse: " + ((ErrorResponse)actual).getMessage()); 102 | } 103 | isInstanceOf(OkResponse.class); 104 | return OkResponseAssert.assertThat((OkResponse) actual); 105 | } 106 | 107 | public static class OkResponseAssert extends AbstractAssert { 108 | public OkResponseAssert(OkResponse actual) { 109 | super(actual, OkResponseAssert.class); 110 | } 111 | 112 | public static OkResponseAssert assertThat(OkResponse actual) { 113 | return new OkResponseAssert(actual); 114 | } 115 | 116 | public OkResponseAssert hasAffectedRows(long affectedRows) { 117 | isNotNull(); 118 | if (affectedRows != actual.getAffectedRows()) { 119 | failWithMessage("Expected affected rows to be <%i> but is <%i>"); 120 | } 121 | return this; 122 | } 123 | } 124 | 125 | public ResultsetRowAssert isResultsetRow() { 126 | isNotNull(); 127 | isInstanceOf(ResultsetRow.class); 128 | return new ResultsetRowAssert((ResultsetRow)actual); 129 | } 130 | 131 | public static class ResultsetRowAssert extends AbstractAssert { 132 | public ResultsetRowAssert(ResultsetRow actual) { 133 | super(actual, ResultsetRowAssert.class); 134 | } 135 | 136 | public AbstractListAssert, String, ObjectAssert> hasValues() { 137 | return Assertions.assertThat(actual.getValues()); 138 | } 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | # avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %* 125 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 126 | if ERRORLEVEL 1 goto error 127 | goto end 128 | 129 | :error 130 | set ERROR_CODE=1 131 | 132 | :end 133 | @endlocal & set ERROR_CODE=%ERROR_CODE% 134 | 135 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 136 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 137 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 138 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 139 | :skipRcPost 140 | 141 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 142 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 143 | 144 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 145 | 146 | exit /B %ERROR_CODE% 147 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/Handshake.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.DefaultByteBufHolder; 21 | import io.netty.util.AsciiString; 22 | 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.EnumSet; 26 | import java.util.Objects; 27 | import java.util.Set; 28 | 29 | /** 30 | * 31 | */ 32 | public class Handshake extends DefaultByteBufHolder implements MysqlServerPacket { 33 | 34 | public static final int DEFAULT_PROTOCOL_VERSION = 10; 35 | 36 | private final int protocolVersion; 37 | private final AsciiString serverVersion; 38 | private final int connectionId; 39 | private final Set capabilities; 40 | private final MysqlCharacterSet characterSet; 41 | private final Set serverStatus; 42 | private final String authPluginName; 43 | 44 | private Handshake(Builder builder) { 45 | super(builder.authPluginData); 46 | if (builder.authPluginData.readableBytes() < Constants.AUTH_PLUGIN_DATA_PART1_LEN) { 47 | throw new IllegalArgumentException("authPluginData can not contain less than " + Constants.AUTH_PLUGIN_DATA_PART1_LEN + " bytes."); 48 | } 49 | protocolVersion = builder.protocolVersion; 50 | if (builder.serverVersion == null) { 51 | throw new NullPointerException("serverVersion can not be null"); 52 | } 53 | serverVersion = AsciiString.of(builder.serverVersion); 54 | connectionId = builder.connectionId; 55 | capabilities = Collections.unmodifiableSet(builder.capabilities); 56 | characterSet = builder.characterSet; 57 | serverStatus = Collections.unmodifiableSet(builder.serverStatus); 58 | authPluginName = builder.authPluginName; 59 | } 60 | 61 | public static Builder builder() { 62 | return new Builder(); 63 | } 64 | 65 | public int getProtocolVersion() { 66 | return protocolVersion; 67 | } 68 | 69 | public AsciiString getServerVersion() { 70 | return serverVersion; 71 | } 72 | 73 | public int getConnectionId() { 74 | return connectionId; 75 | } 76 | 77 | public ByteBuf getAuthPluginData() { 78 | return content(); 79 | } 80 | 81 | public Set getCapabilities() { 82 | return capabilities; 83 | } 84 | 85 | public MysqlCharacterSet getCharacterSet() { 86 | return characterSet; 87 | } 88 | 89 | public Set getServerStatus() { 90 | return serverStatus; 91 | } 92 | 93 | public String getAuthPluginName() { 94 | return authPluginName; 95 | } 96 | 97 | @Override 98 | public int getSequenceId() { 99 | return 0; 100 | } 101 | 102 | @Override 103 | public boolean equals(Object o) { 104 | if (this == o) return true; 105 | if (o == null || getClass() != o.getClass()) return false; 106 | if (!super.equals(o)) return false; 107 | final Handshake handshake = (Handshake) o; 108 | return protocolVersion == handshake.protocolVersion && 109 | connectionId == handshake.connectionId && 110 | Objects.equals(serverVersion, handshake.serverVersion) && 111 | Objects.equals(capabilities, handshake.capabilities) && 112 | characterSet == handshake.characterSet && 113 | Objects.equals(serverStatus, handshake.serverStatus) && 114 | Objects.equals(authPluginName, handshake.authPluginName); 115 | } 116 | 117 | @Override 118 | public int hashCode() { 119 | return Objects.hash(super.hashCode(), protocolVersion, serverVersion, connectionId, capabilities, characterSet, serverStatus, authPluginName); 120 | } 121 | 122 | public static class Builder extends AbstractAuthPluginDataBuilder { 123 | private int protocolVersion = DEFAULT_PROTOCOL_VERSION; 124 | private CharSequence serverVersion; 125 | private int connectionId = -1; 126 | 127 | private MysqlCharacterSet characterSet = MysqlCharacterSet.DEFAULT; 128 | private Set serverStatus = EnumSet.noneOf(ServerStatusFlag.class); 129 | private String authPluginName; 130 | 131 | public Builder protocolVersion(int protocolVerison) { 132 | this.protocolVersion = protocolVerison; 133 | return this; 134 | } 135 | 136 | public Builder serverVersion(CharSequence serverVersion) { 137 | this.serverVersion = serverVersion; 138 | return this; 139 | } 140 | 141 | public Builder connectionId(int connectionId) { 142 | this.connectionId = connectionId; 143 | return this; 144 | } 145 | 146 | public Builder characterSet(MysqlCharacterSet characterSet) { 147 | this.characterSet = characterSet == null ? MysqlCharacterSet.DEFAULT : characterSet; 148 | return this; 149 | } 150 | 151 | public Builder addServerStatus(ServerStatusFlag serverStatus, ServerStatusFlag... serverStatuses) { 152 | this.serverStatus.add(serverStatus); 153 | Collections.addAll(this.serverStatus, serverStatuses); 154 | return this; 155 | } 156 | 157 | public Builder addServerStatus(Collection serverStatus) { 158 | this.serverStatus.addAll(serverStatus); 159 | return this; 160 | } 161 | 162 | public Builder authPluginName(String authPluginName) { 163 | capabilities.add(CapabilityFlags.CLIENT_PLUGIN_AUTH); 164 | this.authPluginName = authPluginName; 165 | return this; 166 | } 167 | 168 | public Handshake build() { 169 | return new Handshake(this); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlServerResultSetPacketDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | 7 | import java.nio.charset.Charset; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** 13 | * 14 | */ 15 | public class MysqlServerResultSetPacketDecoder extends AbstractPacketDecoder implements MysqlServerPacketDecoder { 16 | 17 | enum State { 18 | COLUMN_COUNT, 19 | COLUMN_DEFINITION, 20 | COMPLETE, 21 | ERROR, 22 | ROW 23 | } 24 | 25 | private State state = State.COLUMN_COUNT; 26 | private List columnDefinitions; 27 | 28 | public MysqlServerResultSetPacketDecoder() { 29 | this(DEFAULT_MAX_PACKET_SIZE); 30 | } 31 | 32 | public MysqlServerResultSetPacketDecoder(int maxPacketSize) { 33 | super(maxPacketSize); 34 | } 35 | 36 | @Override 37 | protected void decodePacket(ChannelHandlerContext ctx, int sequenceId, ByteBuf packet, List out) { 38 | final Channel channel = ctx.channel(); 39 | final Set capabilities = CapabilityFlags.getCapabilitiesAttr(channel); 40 | final Charset serverCharset = MysqlCharacterSet.getServerCharsetAttr(channel).getCharset(); 41 | 42 | switch (state) { 43 | case COLUMN_COUNT: 44 | handleColumnCount(sequenceId, packet, out, capabilities, serverCharset); 45 | break; 46 | case COLUMN_DEFINITION: 47 | handleColumngDefinition(sequenceId, packet, out, capabilities, serverCharset); 48 | break; 49 | case ROW: 50 | handleRow(sequenceId, packet, out, capabilities, serverCharset); 51 | break; 52 | case COMPLETE: 53 | throw new IllegalStateException("Received an unexpected packet after decoding a result set"); 54 | case ERROR: 55 | throw new IllegalStateException("Received a packet while in an error state"); 56 | } 57 | } 58 | 59 | private void handleColumnCount(int sequenceId, ByteBuf packet, List out, 60 | Set capabilities, Charset serverCharset) { 61 | final int header = packet.readByte() & 0xff; 62 | if (header == RESPONSE_ERROR) { 63 | state = State.ERROR; 64 | out.add(decodeErrorResponse(sequenceId, packet, serverCharset)); 65 | } else if (header == RESPONSE_OK) { 66 | state = State.COMPLETE; 67 | out.add(decodeOkResponse(sequenceId, packet, capabilities, serverCharset)); 68 | } else { 69 | state = State.COLUMN_DEFINITION; 70 | out.add(decodeFieldCount(sequenceId, packet, header)); 71 | } 72 | } 73 | 74 | private ColumnCount decodeFieldCount(int sequenceId, ByteBuf packet, int header) { 75 | final int currentResultSetFieldCount = (int) CodecUtils.readLengthEncodedInteger(packet, header); 76 | if (currentResultSetFieldCount < 0) { 77 | throw new IllegalStateException("Field count is too large to handle"); 78 | } 79 | columnDefinitions = new ArrayList<>(currentResultSetFieldCount); 80 | return new ColumnCount(sequenceId, currentResultSetFieldCount); 81 | } 82 | 83 | private void handleColumngDefinition(int sequenceId, ByteBuf packet, List out, 84 | Set capabilities, Charset serverCharset) { 85 | final int header = packet.readUnsignedByte(); 86 | if (header == RESPONSE_EOF) { 87 | state = State.ROW; 88 | out.add(decodeEofResponse(sequenceId, packet, capabilities)); 89 | } else { 90 | final ColumnDefinition columnDefinition = decodeColumnDefinition(sequenceId, packet, header, serverCharset); 91 | columnDefinitions.add(columnDefinition); 92 | out.add(columnDefinition); 93 | } 94 | } 95 | 96 | private ColumnDefinition decodeColumnDefinition(int sequenceId, ByteBuf packet, int header, Charset serverCharset) { 97 | final ColumnDefinition.Builder builder = ColumnDefinition 98 | .builder() 99 | .sequenceId(sequenceId) 100 | .catalog(CodecUtils.readLengthEncodedString(packet, header, serverCharset)) 101 | .schema(CodecUtils.readLengthEncodedString(packet, serverCharset)) 102 | .table(CodecUtils.readLengthEncodedString(packet, serverCharset)) 103 | .orgTable(CodecUtils.readLengthEncodedString(packet, serverCharset)) 104 | .name(CodecUtils.readLengthEncodedString(packet, serverCharset)) 105 | .orgName(CodecUtils.readLengthEncodedString(packet, serverCharset)); 106 | packet.readByte(); 107 | builder.characterSet(MysqlCharacterSet.findById(packet.readShortLE())) 108 | .columnLength(packet.readUnsignedIntLE()) 109 | .type(ColumnType.lookup(packet.readUnsignedByte())) 110 | .addFlags(CodecUtils.readShortEnumSet(packet, ColumnFlag.class)) 111 | .decimals(packet.readUnsignedByte()); 112 | packet.skipBytes(2); 113 | return builder.build(); 114 | } 115 | 116 | private void handleRow(int sequenceId, ByteBuf packet, List out, Set capabilities, Charset serverCharset) { 117 | final int header = packet.readByte() & 0xff; 118 | switch (header) { 119 | case RESPONSE_ERROR: 120 | state = State.ERROR; 121 | out.add(decodeErrorResponse(sequenceId, packet, serverCharset)); 122 | break; 123 | case RESPONSE_EOF: 124 | state = State.COMPLETE; 125 | out.add(decodeEofResponse(sequenceId, packet, capabilities)); 126 | break; 127 | case RESPONSE_OK: 128 | state = State.COMPLETE; 129 | decodeOkResponse(sequenceId, packet, capabilities, serverCharset); 130 | break; 131 | default: 132 | decodeRow(sequenceId, packet, header, out); 133 | } 134 | } 135 | 136 | private void decodeRow(int sequenceId, ByteBuf packet, int firstByte, List out) { 137 | final int size = columnDefinitions.size(); 138 | final String[] values = new String[size]; 139 | values[0] = CodecUtils.readLengthEncodedString(packet, firstByte, columnDefinitions.get(0).getCharacterSet().getCharset()); 140 | for (int i = 1; i < size; i++) { 141 | values[i] = CodecUtils.readLengthEncodedString(packet, columnDefinitions.get(i).getCharacterSet().getCharset()); 142 | } 143 | out.add(new ResultsetRow(sequenceId, values)); 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlServerPacketEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufUtil; 21 | import io.netty.channel.ChannelHandlerContext; 22 | 23 | import java.nio.charset.Charset; 24 | import java.util.EnumSet; 25 | 26 | /** 27 | * 28 | */ 29 | public class MysqlServerPacketEncoder extends AbstractPacketEncoder { 30 | 31 | @Override 32 | protected void encodePacket(ChannelHandlerContext ctx, MysqlServerPacket packet, ByteBuf buf) { 33 | final EnumSet capabilities = CapabilityFlags.getCapabilitiesAttr(ctx.channel()); 34 | final Charset serverCharset = MysqlCharacterSet.getServerCharsetAttr(ctx.channel()).getCharset(); 35 | if (packet instanceof ColumnCount) { 36 | encodeColumnCount((ColumnCount) packet, buf); 37 | } else if (packet instanceof ColumnDefinition) { 38 | encodeColumnDefinition(serverCharset, (ColumnDefinition) packet, buf); 39 | } else if (packet instanceof EofResponse) { 40 | encodeEofResponse(capabilities, (EofResponse) packet, buf); 41 | } else if (packet instanceof Handshake) { 42 | encodeHandshake((Handshake)packet, buf); 43 | } else if (packet instanceof OkResponse) { 44 | encodeOkResponse(capabilities, serverCharset, (OkResponse) packet, buf); 45 | } else if (packet instanceof ResultsetRow) { 46 | encodeResultsetRow(serverCharset, (ResultsetRow) packet, buf); 47 | } else { 48 | throw new IllegalStateException("Unknown packet type: " + packet.getClass()); 49 | } 50 | } 51 | 52 | protected void encodeColumnCount(ColumnCount columnCount, ByteBuf buf) { 53 | CodecUtils.writeLengthEncodedInt(buf, (long)columnCount.getFieldCount()); 54 | } 55 | 56 | protected void encodeColumnDefinition(Charset serverCharset, ColumnDefinition packet, ByteBuf buf) { 57 | CodecUtils.writeLengthEncodedString(buf, packet.getCatalog(), serverCharset); 58 | CodecUtils.writeLengthEncodedString(buf, packet.getSchema(), serverCharset); 59 | CodecUtils.writeLengthEncodedString(buf, packet.getTable(), serverCharset); 60 | CodecUtils.writeLengthEncodedString(buf, packet.getOrgTable(), serverCharset); 61 | CodecUtils.writeLengthEncodedString(buf, packet.getName(), serverCharset); 62 | CodecUtils.writeLengthEncodedString(buf, packet.getOrgName(), serverCharset); 63 | buf.writeByte(0x0c); 64 | buf.writeShortLE(packet.getCharacterSet().getId()) 65 | .writeIntLE((int)packet.getColumnLength()) 66 | .writeByte(packet.getType().getValue()) 67 | .writeShortLE((int)CodecUtils.toLong(packet.getFlags())) 68 | .writeByte(packet.getDecimals()) 69 | .writeShort(0); 70 | // TODO Add default values for COM_FIELD_LIST 71 | } 72 | 73 | protected void encodeEofResponse(EnumSet capabilities, EofResponse eof, ByteBuf buf) { 74 | buf.writeByte(0xfe); 75 | if (capabilities.contains(CapabilityFlags.CLIENT_PROTOCOL_41)) { 76 | buf.writeShortLE(eof.getWarnings()) 77 | .writeShortLE((int)CodecUtils.toLong(eof.getStatusFlags())); 78 | } 79 | } 80 | 81 | protected void encodeHandshake(Handshake handshake, ByteBuf buf) { 82 | buf.writeByte(handshake.getProtocolVersion()) 83 | .writeBytes(handshake.getServerVersion().array()) 84 | .writeByte(Constants.NUL_BYTE) 85 | .writeIntLE(handshake.getConnectionId()) 86 | .writeBytes(handshake.getAuthPluginData(), Constants.AUTH_PLUGIN_DATA_PART1_LEN) 87 | .writeByte(Constants.NUL_BYTE) 88 | .writeShortLE((int) CodecUtils.toLong(handshake.getCapabilities())) 89 | .writeByte(handshake.getCharacterSet().getId()) 90 | .writeShortLE((int) CodecUtils.toLong(handshake.getServerStatus())) 91 | .writeShortLE((int) (CodecUtils.toLong(handshake.getCapabilities()) >> Short.SIZE)); 92 | if (handshake.getCapabilities().contains(CapabilityFlags.CLIENT_PLUGIN_AUTH)) { 93 | buf.writeByte(handshake.getAuthPluginData().readableBytes() + Constants.AUTH_PLUGIN_DATA_PART1_LEN); 94 | } else { 95 | buf.writeByte(Constants.NUL_BYTE); 96 | } 97 | buf.writeZero(Constants.HANDSHAKE_RESERVED_BYTES); 98 | if (handshake.getCapabilities().contains(CapabilityFlags.CLIENT_SECURE_CONNECTION)) { 99 | final int padding = Constants.AUTH_PLUGIN_DATA_PART2_MIN_LEN - handshake.getAuthPluginData().readableBytes(); 100 | buf.writeBytes(handshake.getAuthPluginData()); 101 | if (padding > 0) { 102 | buf.writeZero(padding); 103 | } 104 | } 105 | if (handshake.getCapabilities().contains(CapabilityFlags.CLIENT_PLUGIN_AUTH)) { 106 | ByteBufUtil.writeUtf8(buf, handshake.getAuthPluginName()); 107 | buf.writeByte(Constants.NUL_BYTE); 108 | } 109 | } 110 | 111 | protected void encodeOkResponse(EnumSet capabilities, Charset serverCharset, OkResponse response, ByteBuf buf) { 112 | buf.writeByte(0); 113 | CodecUtils.writeLengthEncodedInt(buf, response.getAffectedRows()); 114 | CodecUtils.writeLengthEncodedInt(buf, response.getLastInsertId()); 115 | if (capabilities.contains(CapabilityFlags.CLIENT_PROTOCOL_41)) { 116 | buf.writeShortLE((int)CodecUtils.toLong(response.getStatusFlags())) 117 | .writeShortLE(response.getWarnings()); 118 | 119 | } else if (capabilities.contains(CapabilityFlags.CLIENT_TRANSACTIONS)) { 120 | buf.writeShortLE((int)CodecUtils.toLong(response.getStatusFlags())); 121 | } 122 | if (capabilities.contains(CapabilityFlags.CLIENT_SESSION_TRACK)) { 123 | CodecUtils.writeLengthEncodedString(buf, response.getInfo(), serverCharset); 124 | if (response.getStatusFlags().contains(ServerStatusFlag.SESSION_STATE_CHANGED)) { 125 | CodecUtils.writeLengthEncodedString(buf, response.getSessionStateChanges(), serverCharset); 126 | } 127 | } else { 128 | if (response.getInfo() != null) { 129 | buf.writeCharSequence(response.getInfo(), serverCharset); 130 | } 131 | } 132 | } 133 | 134 | protected void encodeResultsetRow(Charset serverCharset, ResultsetRow packet, ByteBuf buf) { 135 | for (String value : packet.getValues()) { 136 | CodecUtils.writeLengthEncodedString(buf, value, serverCharset); 137 | } 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/CodecUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.Unpooled; 21 | import io.netty.handler.codec.CodecException; 22 | import io.netty.util.AsciiString; 23 | 24 | import java.nio.charset.Charset; 25 | import java.util.EnumSet; 26 | import java.util.Set; 27 | 28 | final class CodecUtils { 29 | 30 | static final int NULL_VALUE = 0xfb; 31 | static final int SHORT_VALUE = 0xfc; 32 | static final int MEDIUM_VALUE = 0xfd; 33 | static final int LONG_VALUE = 0xfe; 34 | 35 | private CodecUtils() { 36 | // Non-instantiable 37 | } 38 | 39 | static long readLengthEncodedInteger(ByteBuf buf) { 40 | return readLengthEncodedInteger(buf, buf.readUnsignedByte()); 41 | } 42 | 43 | // https://dev.mysql.com/doc/internals/en/integer.html 44 | static long readLengthEncodedInteger(ByteBuf buf, int firstByte) { 45 | firstByte = firstByte & 0xff; 46 | if (firstByte < NULL_VALUE) { 47 | return firstByte; 48 | } 49 | if (firstByte == NULL_VALUE) { 50 | return -1; 51 | } 52 | if (firstByte == SHORT_VALUE) { 53 | return buf.readUnsignedShortLE(); 54 | } 55 | if (firstByte == MEDIUM_VALUE) { 56 | return buf.readUnsignedMediumLE(); 57 | } 58 | if (firstByte == LONG_VALUE) { 59 | final long length = buf.readLongLE(); 60 | if (length < 0) { 61 | throw new CodecException("Received a length value too large to handle: " + Long.toHexString(length)); 62 | } 63 | return length; 64 | } 65 | throw new CodecException("Received an invalid length value " + firstByte); 66 | } 67 | 68 | // static String readLengthEncodedString(ByteBuf buf, Charset charset) { 69 | // return readLengthEncodedString(buf, buf.readByte(), charset); 70 | // } 71 | // 72 | // static String readLengthEncodedString(ByteBuf buf, int firstByte, Charset charset) { 73 | // final long length = readLengthEncodedInteger(buf, firstByte); 74 | // return readFixedLengthString(buf, (int) length, charset); 75 | // } 76 | // 77 | static AsciiString readNullTerminatedString(ByteBuf buf) { 78 | final int len = findNullTermLen(buf); 79 | if (len < 0) { 80 | return null; 81 | } 82 | final AsciiString s = readFixedLengthString(buf, len); 83 | buf.readByte(); 84 | return s; 85 | } 86 | 87 | static String readNullTerminatedString(ByteBuf buf, Charset charset) { 88 | final int len = findNullTermLen(buf); 89 | if (len < 0) { 90 | return null; 91 | } 92 | final String s = readFixedLengthString(buf, len, charset); 93 | buf.readByte(); 94 | return s; 95 | } 96 | 97 | static int findNullTermLen(ByteBuf buf) { 98 | final int termIdx = buf.indexOf(buf.readerIndex(), buf.capacity(), (byte) 0); 99 | if (termIdx < 0) { 100 | return -1; 101 | } 102 | return termIdx - buf.readerIndex(); 103 | } 104 | 105 | static AsciiString readFixedLengthString(ByteBuf buf, int len) { 106 | final byte[] bytes = new byte[len]; 107 | buf.readBytes(bytes); 108 | return new AsciiString(bytes); 109 | } 110 | 111 | static String readFixedLengthString(ByteBuf buf, int length, Charset charset) { 112 | if (length < 0) { 113 | return null; 114 | } 115 | final String s = buf.toString(buf.readerIndex(), length, charset); 116 | buf.readerIndex(buf.readerIndex() + length); 117 | return s; 118 | } 119 | 120 | static String readLengthEncodedString(ByteBuf buf, Charset charset) { 121 | final long len = readLengthEncodedInteger(buf); 122 | return readFixedLengthString(buf, (int) len, charset); 123 | } 124 | 125 | static String readLengthEncodedString(ByteBuf buf, int firstByte, Charset charset) { 126 | final long len = readLengthEncodedInteger(buf, firstByte); 127 | return readFixedLengthString(buf, (int) len, charset); 128 | } 129 | 130 | static > EnumSet readShortEnumSet(ByteBuf buf, Class enumClass) { 131 | return toEnumSet(enumClass, buf.readUnsignedShortLE()); 132 | } 133 | 134 | static > EnumSet readIntEnumSet(ByteBuf buf, Class enumClass) { 135 | return toEnumSet(enumClass, buf.readUnsignedIntLE()); 136 | } 137 | 138 | static > EnumSet toEnumSet(Class enumClass, long vector) { 139 | EnumSet set = EnumSet.noneOf(enumClass); 140 | for (E e : enumClass.getEnumConstants()) { 141 | final long mask = 1 << e.ordinal(); 142 | if ((mask & vector) != 0) { 143 | set.add(e); 144 | } 145 | } 146 | return set; 147 | } 148 | 149 | // private static E toEnum(Class enumClass, int i) { 150 | // E[] enumConstants = enumClass.getEnumConstants(); 151 | // if (i > enumConstants.length) { 152 | // throw new IndexOutOfBoundsException(String.format( 153 | // "%d is too large of an ordinal to convert to the enum %s", 154 | // i, enumClass.getName())); 155 | // } 156 | // return enumConstants[i]; 157 | // } 158 | 159 | static > long toLong(Set set) { 160 | long vector = 0; 161 | for (E e : set) { 162 | if (e.ordinal() >= Long.SIZE) { 163 | throw new IllegalArgumentException("The enum set is too large to fit in a bit vector: " + set); 164 | } 165 | vector |= 1L << e.ordinal(); 166 | } 167 | return vector; 168 | } 169 | 170 | static void writeLengthEncodedInt(ByteBuf buf, Long n) { 171 | if (n == null) { 172 | buf.writeByte(NULL_VALUE); 173 | } else if (n < 0) { 174 | throw new IllegalArgumentException("Cannot encode a negative length: " + n); 175 | } else if (n < NULL_VALUE) { 176 | buf.writeByte(n.intValue()); 177 | } else if (n < 0xffff) { 178 | buf.writeByte(SHORT_VALUE); 179 | buf.writeShortLE(n.intValue()); 180 | } else if (n < 0xffffff) { 181 | buf.writeByte(MEDIUM_VALUE); 182 | buf.writeMediumLE(n.intValue()); 183 | } else { 184 | buf.writeByte(LONG_VALUE); 185 | buf.writeLongLE(n); 186 | } 187 | } 188 | 189 | static void writeLengthEncodedString(ByteBuf buf, CharSequence sequence, Charset charset) { 190 | final ByteBuf tmpBuf = Unpooled.buffer(); 191 | try { 192 | tmpBuf.writeCharSequence(sequence, charset); 193 | writeLengthEncodedInt(buf, (long) tmpBuf.readableBytes()); 194 | buf.writeBytes(tmpBuf); 195 | } finally { 196 | tmpBuf.release(); 197 | } 198 | } 199 | 200 | static void writeNullTerminatedString(ByteBuf buf, CharSequence sequence, Charset charset) { 201 | if (sequence != null) { 202 | buf.writeCharSequence(sequence, charset); 203 | } 204 | buf.writeByte(0); 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/integration/client/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql.integration.client; 18 | 19 | import java.util.EnumSet; 20 | import java.util.concurrent.BlockingQueue; 21 | import java.util.concurrent.LinkedBlockingQueue; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | import com.github.mheath.netty.codec.mysql.Constants; 25 | import com.github.mheath.netty.codec.mysql.Handshake; 26 | import com.github.mheath.netty.codec.mysql.HandshakeResponse; 27 | import com.github.mheath.netty.codec.mysql.MysqlClientPacketEncoder; 28 | import com.github.mheath.netty.codec.mysql.MysqlPacketAssert; 29 | import com.github.mheath.netty.codec.mysql.MysqlServerPacket; 30 | import com.github.mheath.netty.codec.mysql.MysqlServerPacketDecoder; 31 | import com.github.mheath.netty.codec.mysql.QueryCommand; 32 | import io.netty.bootstrap.Bootstrap; 33 | import io.netty.channel.Channel; 34 | import io.netty.channel.ChannelFuture; 35 | import io.netty.channel.ChannelFutureListener; 36 | import io.netty.channel.ChannelHandlerContext; 37 | import io.netty.channel.ChannelInboundHandlerAdapter; 38 | import io.netty.channel.ChannelInitializer; 39 | import io.netty.channel.EventLoopGroup; 40 | import io.netty.channel.nio.NioEventLoopGroup; 41 | import io.netty.channel.socket.SocketChannel; 42 | import io.netty.channel.socket.nio.NioSocketChannel; 43 | import com.github.mheath.netty.codec.mysql.CapabilityFlags; 44 | import com.github.mheath.netty.codec.mysql.MysqlClientPacket; 45 | import com.github.mheath.netty.codec.mysql.MysqlNativePasswordUtil; 46 | import com.github.mheath.netty.codec.mysql.MysqlServerConnectionPacketDecoder; 47 | import com.github.mheath.netty.codec.mysql.MysqlServerResultSetPacketDecoder; 48 | import org.junit.jupiter.api.AfterAll; 49 | import org.junit.jupiter.api.BeforeAll; 50 | 51 | import static org.assertj.core.api.Assertions.fail; 52 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 53 | 54 | /** 55 | * 56 | */ 57 | abstract class AbstractIntegrationTest { 58 | 59 | static String serverHost; 60 | static int serverPort; 61 | static String user; 62 | static String password; 63 | static String database; 64 | 65 | static SimpleClient client; 66 | 67 | @BeforeAll 68 | public static void init() { 69 | serverHost = getProperty("mysql.host"); 70 | serverPort = Integer.getInteger("mysql.port", 3306); 71 | user = getProperty("mysql.user"); 72 | password = getProperty("mysql.password"); 73 | database = getProperty("mysql.database"); 74 | 75 | client = new SimpleClient(); 76 | } 77 | 78 | private static String getProperty(String property) { 79 | final String value = System.getProperty(property); 80 | assumeTrue(value != null && value.trim().length() != 0, property + " property is missing"); 81 | return value; 82 | } 83 | 84 | @AfterAll 85 | public static void cleanup() { 86 | if (client != null) { 87 | client.close(); 88 | } 89 | } 90 | 91 | protected static final EnumSet CLIENT_CAPABILITIES = CapabilityFlags.getImplicitCapabilities(); 92 | static { 93 | CLIENT_CAPABILITIES.addAll(EnumSet.of(CapabilityFlags.CLIENT_PLUGIN_AUTH, CapabilityFlags.CLIENT_SECURE_CONNECTION, CapabilityFlags.CLIENT_CONNECT_WITH_DB)); 94 | } 95 | 96 | protected static class SimpleClient implements AutoCloseable { 97 | 98 | private final EventLoopGroup eventLoopGroup; 99 | private final Bootstrap bootstrap; 100 | 101 | public SimpleClient() { 102 | eventLoopGroup = new NioEventLoopGroup(); 103 | bootstrap = new Bootstrap(); 104 | bootstrap.group(eventLoopGroup); 105 | bootstrap.channel(NioSocketChannel.class); 106 | bootstrap.handler(new ChannelInitializer() { 107 | @Override 108 | public void initChannel(SocketChannel ch) throws Exception { 109 | CapabilityFlags.setCapabilitiesAttr(ch, CLIENT_CAPABILITIES); 110 | ch.pipeline().addLast(new MysqlServerConnectionPacketDecoder()); 111 | ch.pipeline().addLast(new MysqlClientPacketEncoder()); 112 | } 113 | }); 114 | 115 | } 116 | 117 | public void close() { 118 | eventLoopGroup.shutdownGracefully(); 119 | } 120 | 121 | public Connection connect() { 122 | return new Connection(bootstrap.connect(serverHost, serverPort)); 123 | } 124 | 125 | } 126 | 127 | protected static class Connection implements AutoCloseable { 128 | private final Channel channel; 129 | private final BlockingQueue serverPackets = new LinkedBlockingQueue(); 130 | 131 | private Handshake handshake; 132 | 133 | Connection(ChannelFuture connectFuture) { 134 | try { 135 | connectFuture.addListener((ChannelFutureListener) future -> { 136 | if (future.isSuccess()) { 137 | future.channel().pipeline().addLast(new ChannelInboundHandlerAdapter() { 138 | @Override 139 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 140 | if (msg instanceof Handshake) { 141 | CapabilityFlags.getCapabilitiesAttr(ctx.channel()).retainAll(((Handshake) msg).getCapabilities()); 142 | } 143 | serverPackets.add((MysqlServerPacket) msg); 144 | } 145 | }); 146 | } 147 | }); 148 | connectFuture = connectFuture.sync(); 149 | if (!connectFuture.isSuccess()) { 150 | throw new RuntimeException(connectFuture.cause()); 151 | } 152 | channel = connectFuture.channel(); 153 | } catch (InterruptedException e) { 154 | throw new RuntimeException(e); 155 | } 156 | } 157 | 158 | MysqlServerPacket pollServer() { 159 | try { 160 | final MysqlServerPacket packet = serverPackets.poll(5, TimeUnit.SECONDS); 161 | if (packet == null) { 162 | fail("Timed out waiting for packet from server."); 163 | } 164 | return packet; 165 | } catch (InterruptedException e) { 166 | throw new RuntimeException(e); 167 | } 168 | } 169 | 170 | MysqlPacketAssert assertThatNextPacket() { 171 | return new MysqlPacketAssert(pollServer()); 172 | } 173 | 174 | public ChannelFuture write(MysqlClientPacket packet) { 175 | return channel.writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 176 | } 177 | 178 | public ChannelFuture query(String query) { 179 | channel.pipeline().replace(MysqlServerPacketDecoder.class, "decoder", new MysqlServerResultSetPacketDecoder()); 180 | return write(new QueryCommand(0, query)); 181 | } 182 | 183 | public void authenticate() { 184 | final Handshake handshake = (Handshake) pollServer(); 185 | 186 | final HandshakeResponse response = HandshakeResponse 187 | .create() 188 | .addCapabilities(CLIENT_CAPABILITIES) 189 | .username(user) 190 | .addAuthData(MysqlNativePasswordUtil.hashPassword(password, handshake.getAuthPluginData())) 191 | .database(database) 192 | .authPluginName(Constants.MYSQL_NATIVE_PASSWORD) 193 | .build(); 194 | write(response); 195 | assertThatNextPacket().isOkResponse(); 196 | } 197 | 198 | @Override 199 | public void close() { 200 | try { 201 | channel.close().sync(); 202 | } catch (InterruptedException e) { 203 | throw new RuntimeException(e); 204 | } 205 | } 206 | 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /codec/src/main/java/com/github/mheath/netty/codec/mysql/MysqlCharacterSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.channel.Channel; 20 | import io.netty.util.AttributeKey; 21 | 22 | import java.nio.charset.Charset; 23 | import java.nio.charset.UnsupportedCharsetException; 24 | 25 | public enum MysqlCharacterSet { 26 | BIG5_CHINESE_CI((byte) 1, "Big5"), 27 | LATIN2_CZECH_CS((byte) 2, "ISO8859_2"), 28 | DEC8_SWEDISH_CI((byte) 3, "ISO8859_1"), 29 | CP850_GENERAL_CI((byte) 4, "Cp437"), 30 | LATIN1_GERMAN1_CI((byte) 5, "ISO8859_1"), 31 | HP8_ENGLISH_CI((byte) 6, "ISO8859_1"), 32 | KOI8R_GENERAL_CI((byte) 7, "KOI8_R"), 33 | LATIN1_SWEDISH_CI((byte) 8, "ISO8859_1"), 34 | LATIN2_GENERAL_CI((byte) 9, "ISO8859_2"), 35 | SWE7_SWEDISH_CI((byte) 10, "ISO8859_1"), 36 | ASCII_GENERAL_CI((byte) 11, "US-ASCII"), 37 | UJIS_JAPANESE_CI((byte) 12, "EUC_JP"), 38 | SJIS_JAPANESE_CI((byte) 13, "SJIS"), 39 | CP1251_BULGARIAN_CI((byte) 14, "Cp1251"), 40 | LATIN1_DANISH_CI((byte) 15, "ISO8859_1"), 41 | HEBREW_GENERAL_CI((byte) 16, "HEBREW"), 42 | TIS620_THAI_CI((byte) 18, "TIS620"), 43 | EUCKR_KOREAN_CI((byte) 19, "EUCKR"), 44 | LATIN7_ESTONIAN_CS((byte) 20, "ISO8859_7"), 45 | LATIN2_HUNGARIAN_CI((byte) 21, "ISO8859_2"), 46 | KOI8U_GENERAL_CI((byte) 22, "KOI8_R"), 47 | CP1251_UKRAINIAN_CI((byte) 23, "Cp1251"), 48 | GB2312_CHINESE_CI((byte) 24, "GB2312"), 49 | GREEK_GENERAL_CI((byte) 25, "GREEK"), 50 | CP1250_GENERAL_CI((byte) 26, "Cp1250"), 51 | LATIN2_CROATIAN_CI((byte) 27, "ISO8859_2"), 52 | GBK_CHINESE_CI((byte) 28, "GBK"), 53 | CP1257_LITHUANIAN_CI((byte) 29, "Cp1257"), 54 | LATIN5_TURKISH_CI((byte) 30, "LATIN5"), 55 | LATIN1_GERMAN2_CI((byte) 31, "ISO8859_1"), 56 | ARMSCII8_GENERAL_CI((byte) 32, "ISO8859_1"), 57 | UTF8_GENERAL_CI((byte) 33, "UTF-8"), 58 | CP1250_CZECH_CS((byte) 34, "Cp1250"), 59 | UCS2_GENERAL_CI((byte) 35, "UTF-16BE"), 60 | CP866_GENERAL_CI((byte) 36, "Cp866"), 61 | KEYBCS2_GENERAL_CI((byte) 37, "Cp895"), 62 | MACCE_GENERAL_CI((byte) 38, "MacCentralEurope"), 63 | MACROMAN_GENERAL_CI((byte) 39, "MacRoman"), 64 | CP852_GENERAL_CI((byte) 40, "LATIN2"), 65 | LATIN7_GENERAL_CI((byte) 41, "ISO8859_7"), 66 | LATIN7_GENERAL_CS((byte) 42, "ISO8859_7"), 67 | MACCE_BIN((byte) 43, "MacCentralEurope"), 68 | CP1250_CROATIAN_CI((byte) 44, "Cp1250"), 69 | LATIN1_BIN((byte) 47, "ISO8859_1"), 70 | LATIN1_GENERAL_CI((byte) 48, "ISO8859_1"), 71 | LATIN1_GENERAL_CS((byte) 49, "ISO8859_1"), 72 | CP1251_BIN((byte) 50, "Cp1251"), 73 | CP1251_GENERAL_CI((byte) 51, "Cp1251"), 74 | CP1251_GENERAL_CS((byte) 52, "Cp1251"), 75 | MACROMAN_BIN((byte) 53, "MacRoman"), 76 | CP1256_GENERAL_CI((byte) 57, "Cp1256"), 77 | CP1257_BIN((byte) 58, "Cp1257"), 78 | CP1257_GENERAL_CI((byte) 59, "Cp1257"), 79 | BINARY((byte) 63, "US-ASCII"), 80 | ARMSCII8_BIN((byte) 64, "ISO8859_2"), 81 | ASCII_BIN((byte) 65, "ASCII"), 82 | CP1250_BIN((byte) 66, "Cp1250"), 83 | CP1256_BIN((byte) 67, "Cp1256"), 84 | CP866_BIN((byte) 68, "Cp866"), 85 | DEC8_BIN((byte) 69, "US-ASCII"), 86 | GREEK_BIN((byte) 70, "GREEK"), 87 | HEBREW_BIN((byte) 71, "HEBREW"), 88 | HP8_BIN((byte) 72, "US-ASCII"), 89 | KEYBCS2_BIN((byte) 73, "Cp895"), 90 | KOI8R_BIN((byte) 74, "KOI8_R"), 91 | KOI8U_BIN((byte) 75, "KOI8_R"), 92 | LATIN2_BIN((byte) 77, "ISO8859_2"), 93 | LATIN5_BIN((byte) 78, "LATIN5"), 94 | LATIN7_BIN((byte) 79, "ISO8859_7"), 95 | CP850_BIN((byte) 80, "Cp437"), 96 | CP852_BIN((byte) 81, "Cp852"), 97 | SWE7_BIN((byte) 82, "ISO8859_1"), 98 | UTF8_BIN((byte) 83, "UTF-8"), 99 | BIG5_BIN((byte) 84, "Big5"), 100 | EUCKR_BIN((byte) 85, "EUCKR"), 101 | GB2312_BIN((byte) 86, "GB2312"), 102 | GBK_BIN((byte) 87, "GBK"), 103 | SJIS_BIN((byte) 88, "SJIS"), 104 | TIS620_BIN((byte) 89, "TIS620"), 105 | UCS2_BIN((byte) 90, "UTF-16BE"), 106 | UJIS_BIN((byte) 91, "EUC_JP"), 107 | GEOSTD8_GENERAL_CI((byte) 92, "US-ASCII"), 108 | GEOSTD8_BIN((byte) 93, "US-ASCII"), 109 | LATIN1_SPANISH_CI((byte) 94, "ISO8859_1"), 110 | CP932_JAPANESE_CI((byte) 95, "CP932"), 111 | CP932_BIN((byte) 96, "CP932"), 112 | EUCJPMS_JAPANESE_CI((byte) 97, "EUC_JP_Solaris"), 113 | EUCJPMS_BIN((byte) 98, "EUC_JP_Solaris"), 114 | UCS2_UNICODE_CI((byte) 128, "UTF-16BE"), 115 | UCS2_ICELANDIC_CI((byte) 129, "UTF-16BE"), 116 | UCS2_LATVIAN_CI((byte) 130, "UTF-16BE"), 117 | UCS2_ROMANIAN_CI((byte) 131, "UTF-16BE"), 118 | UCS2_SLOVENIAN_CI((byte) 132, "UTF-16BE"), 119 | UCS2_POLISH_CI((byte) 133, "UTF-16BE"), 120 | UCS2_ESTONIAN_CI((byte) 134, "UTF-16BE"), 121 | UCS2_SPANISH_CI((byte) 135, "UTF-16BE"), 122 | UCS2_SWEDISH_CI((byte) 136, "UTF-16BE"), 123 | UCS2_TURKISH_CI((byte) 137, "UTF-16BE"), 124 | UCS2_CZECH_CI((byte) 138, "UTF-16BE"), 125 | UCS2_DANISH_CI((byte) 139, "UTF-16BE"), 126 | UCS2_LITHUANIAN_CI((byte) 140, "UTF-16BE"), 127 | UCS2_SLOVAK_CI((byte) 141, "UTF-16BE"), 128 | UCS2_SPANISH2_CI((byte) 142, "UTF-16BE"), 129 | UCS2_ROMAN_CI((byte) 143, "UTF-16BE"), 130 | UCS2_PERSIAN_CI((byte) 144, "UTF-16BE"), 131 | UCS2_ESPERANTO_CI((byte) 145, "UTF-16BE"), 132 | UCS2_HUNGARIAN_CI((byte) 146, "UTF-16BE"), 133 | UTF8_UNICODE_CI((byte) 192, "UTF-8"), 134 | UTF8_ICELANDIC_CI((byte) 193, "UTF-8"), 135 | UTF8_LATVIAN_CI((byte) 194, "UTF-8"), 136 | UTF8_ROMANIAN_CI((byte) 195, "UTF-8"), 137 | UTF8_SLOVENIAN_CI((byte) 196, "UTF-8"), 138 | UTF8_POLISH_CI((byte) 197, "UTF-8"), 139 | UTF8_ESTONIAN_CI((byte) 198, "UTF-8"), 140 | UTF8_SPANISH_CI((byte) 199, "UTF-8"), 141 | UTF8_SWEDISH_CI((byte) 200, "UTF-8"), 142 | UTF8_TURKISH_CI((byte) 201, "UTF-8"), 143 | UTF8_CZECH_CI((byte) 202, "UTF-8"), 144 | UTF8_DANISH_CI((byte) 203, "UTF-8"), 145 | UTF8_LITHUANIAN_CI((byte) 204, "UTF-8"), 146 | UTF8_SLOVAK_CI((byte) 205, "UTF-8"), 147 | UTF8_SPANISH2_CI((byte) 206, "UTF-8"), 148 | UTF8_ROMAN_CI((byte) 207, "UTF-8"), 149 | UTF8_PERSIAN_CI((byte) 208, "UTF-8"), 150 | UTF8_ESPERANTO_CI((byte) 209, "UTF-8"), 151 | UTF8_HUNGARIAN_CI((byte) 210, "UTF-8"); 152 | 153 | public static final MysqlCharacterSet DEFAULT = UTF8_GENERAL_CI; 154 | 155 | private byte id; 156 | private Charset charset; 157 | 158 | MysqlCharacterSet(byte id, String charsetName) { 159 | this.id = id; 160 | try { 161 | this.charset = Charset.forName(charsetName); 162 | } catch (UnsupportedCharsetException e) { 163 | this.charset = null; 164 | } 165 | } 166 | 167 | public static MysqlCharacterSet findById(int id) { 168 | for (MysqlCharacterSet charset : values()) { 169 | if (charset.id == id) { 170 | return charset; 171 | } 172 | } 173 | return null; 174 | } 175 | 176 | public Charset getCharset() { 177 | return charset; 178 | } 179 | 180 | public byte getId() { 181 | return id; 182 | } 183 | 184 | private static final AttributeKey SERVER_CHARSET_KEY = AttributeKey.newInstance(MysqlCharacterSet.class.getName() + "-server"); 185 | private static final AttributeKey CLIENT_CHARSET_KEY = AttributeKey.newInstance(MysqlCharacterSet.class.getName() + "-client"); 186 | 187 | public static MysqlCharacterSet getServerCharsetAttr(Channel channel) { 188 | return getCharSetAttr(SERVER_CHARSET_KEY, channel); 189 | } 190 | 191 | public static MysqlCharacterSet getClientCharsetAttr(Channel channel) { 192 | return getCharSetAttr(CLIENT_CHARSET_KEY, channel); 193 | } 194 | 195 | private static MysqlCharacterSet getCharSetAttr(AttributeKey key, Channel channel) { 196 | if (channel.hasAttr(key)) { 197 | return channel.attr(key).get(); 198 | } 199 | return DEFAULT; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # traverses directory structure from process work directory to filesystem root 188 | # first directory with .mvn subdirectory is considered project base directory 189 | find_maven_basedir() { 190 | local basedir=$(pwd) 191 | local wdir=$(pwd) 192 | while [ "$wdir" != '/' ] ; do 193 | if [ -d "$wdir"/.mvn ] ; then 194 | basedir=$wdir 195 | break 196 | fi 197 | wdir=$(cd "$wdir/.."; pwd) 198 | done 199 | echo "${basedir}" 200 | } 201 | 202 | # concatenates all lines of a file 203 | concat_lines() { 204 | if [ -f "$1" ]; then 205 | echo "$(tr -s '\n' ' ' < "$1")" 206 | fi 207 | } 208 | 209 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 210 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 211 | 212 | # For Cygwin, switch paths to Windows format before running java 213 | if $cygwin; then 214 | [ -n "$M2_HOME" ] && 215 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 216 | [ -n "$JAVA_HOME" ] && 217 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 218 | [ -n "$CLASSPATH" ] && 219 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 220 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 221 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 222 | fi 223 | 224 | # Provide a "standardized" way to retrieve the CLI args that will 225 | # work with both Windows and non-Windows executions. 226 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 227 | export MAVEN_CMD_LINE_ARGS 228 | 229 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 230 | 231 | # avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@ 232 | exec "$JAVACMD" \ 233 | $MAVEN_OPTS \ 234 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 235 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 236 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 237 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/HandshakeDecodeEncodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package com.github.mheath.netty.codec.mysql; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.embedded.EmbeddedChannel; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.EnumSet; 24 | import java.util.Set; 25 | 26 | /** 27 | * 28 | */ 29 | public class HandshakeDecodeEncodeTest { 30 | 31 | private static final String EXAMPLE_SERVER_VERSION = "5.5.2-m2"; 32 | private static final int EXAMPLE_CONNECTION_ID = 11; 33 | private static final Set EXAMPLE_CAPABILITIES = EnumSet.of( 34 | CapabilityFlags.CLIENT_LONG_PASSWORD, 35 | CapabilityFlags.CLIENT_FOUND_ROWS, 36 | CapabilityFlags.CLIENT_LONG_FLAG, 37 | CapabilityFlags.CLIENT_CONNECT_WITH_DB, 38 | CapabilityFlags.CLIENT_NO_SCHEMA, 39 | CapabilityFlags.CLIENT_COMPRESS, 40 | CapabilityFlags.CLIENT_ODBC, 41 | CapabilityFlags.CLIENT_LOCAL_FILES, 42 | CapabilityFlags.CLIENT_IGNORE_SPACE, 43 | CapabilityFlags.CLIENT_PROTOCOL_41, 44 | CapabilityFlags.CLIENT_INTERACTIVE, 45 | CapabilityFlags.CLIENT_IGNORE_SIGPIPE, 46 | CapabilityFlags.CLIENT_TRANSACTIONS, 47 | CapabilityFlags.CLIENT_RESERVED, 48 | CapabilityFlags.CLIENT_SECURE_CONNECTION 49 | ); 50 | 51 | @Test 52 | public void handshake() { 53 | final Handshake handshake = Handshake 54 | .builder() 55 | .addCapabilities(EXAMPLE_CAPABILITIES) 56 | .characterSet(MysqlCharacterSet.LATIN1_SWEDISH_CI) 57 | .connectionId(EXAMPLE_CONNECTION_ID) 58 | .serverVersion(EXAMPLE_SERVER_VERSION) 59 | .addServerStatus(ServerStatusFlag.AUTO_COMMIT) 60 | .addAuthData(new byte[]{0x64, 0x76, 0x48, 0x40, 0x49, 0x2d, 0x43, 0x4a, 0x2a, 0x34, 0x64, 61 | 0x7c, 0x63, 0x5a, 0x77, 0x6b, 0x34, 0x5e, 0x5d, 0x3a, 0x00}) 62 | .build(); 63 | 64 | final EmbeddedChannel embeddedChannel = new EmbeddedChannel( 65 | new MysqlServerConnectionPacketDecoder(), 66 | new MysqlServerPacketEncoder()); 67 | CapabilityFlags.setCapabilitiesAttr(embeddedChannel, EXAMPLE_CAPABILITIES); 68 | new MessageDecodeEncodeTester() 69 | .pipeline(embeddedChannel) 70 | .byteBuf(exampleHandshakePacket()) 71 | .addMessage(handshake) 72 | .assertDecode() 73 | .assertEncode(); 74 | } 75 | 76 | private static final String EXAMPLE_WITH_PLUGIN_AUTH_SERVER_VERSION = "5.6.4-m7-log"; 77 | private static final int EXAMPLE_WITH_PLUGIN_AUTH_CONNECTION_ID = 2646; 78 | private static final Set EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES = EnumSet.copyOf(EXAMPLE_CAPABILITIES); 79 | 80 | static { 81 | EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES.add(CapabilityFlags.CLIENT_SSL); 82 | EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES.add(CapabilityFlags.CLIENT_MULTI_STATEMENTS); 83 | EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES.add(CapabilityFlags.CLIENT_MULTI_RESULTS); 84 | EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES.add(CapabilityFlags.CLIENT_PS_MULTI_RESULTS); 85 | EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES.add(CapabilityFlags.CLIENT_PLUGIN_AUTH); 86 | } 87 | 88 | @Test 89 | public void handshakeWithClientPluginAuth() { 90 | final Handshake handshake = Handshake 91 | .builder() 92 | .addCapabilities(EXAMPLE_WITH_PLUGIN_AUTH_CAPABILITIES) 93 | .addAuthData(new byte[]{ 94 | 0x52, 0x42, 0x33, 0x76, 0x7a, 0x26, 0x47, 0x72, 95 | 0x2b, 0x79, 0x44, 0x26, 0x2f, 0x5a, 0x5a, 0x33, 96 | 0x30, 0x35, 0x5a, 0x47, 0x00}) 97 | .authPluginName(Constants.MYSQL_NATIVE_PASSWORD) 98 | .characterSet(MysqlCharacterSet.LATIN1_SWEDISH_CI) 99 | .connectionId(EXAMPLE_WITH_PLUGIN_AUTH_CONNECTION_ID) 100 | .addServerStatus(ServerStatusFlag.AUTO_COMMIT) 101 | .serverVersion(EXAMPLE_WITH_PLUGIN_AUTH_SERVER_VERSION) 102 | .build(); 103 | 104 | new MessageDecodeEncodeTester() 105 | .pipeline(new EmbeddedChannel( 106 | new MysqlServerConnectionPacketDecoder(), 107 | new MysqlServerPacketEncoder())) 108 | .byteBuf(exampleHandshakePacketWithPluginAuth()) 109 | .addMessage(handshake) 110 | .assertDecode() 111 | .assertEncode(); 112 | } 113 | 114 | @Test 115 | public void handshake_5_7_10() { 116 | final Handshake handshake = Handshake 117 | .builder() 118 | .addCapabilities( 119 | CapabilityFlags.CLIENT_LONG_PASSWORD, 120 | CapabilityFlags.CLIENT_FOUND_ROWS, 121 | CapabilityFlags.CLIENT_LONG_FLAG, 122 | CapabilityFlags.CLIENT_CONNECT_WITH_DB, 123 | CapabilityFlags.CLIENT_NO_SCHEMA, 124 | CapabilityFlags.CLIENT_COMPRESS, 125 | CapabilityFlags.CLIENT_ODBC, 126 | CapabilityFlags.CLIENT_LOCAL_FILES, 127 | CapabilityFlags.CLIENT_IGNORE_SPACE, 128 | CapabilityFlags.CLIENT_INTERACTIVE, 129 | CapabilityFlags.CLIENT_SSL, 130 | CapabilityFlags.CLIENT_IGNORE_SIGPIPE, 131 | CapabilityFlags.CLIENT_TRANSACTIONS, 132 | CapabilityFlags.CLIENT_RESERVED, 133 | CapabilityFlags.CLIENT_SECURE_CONNECTION, 134 | CapabilityFlags.CLIENT_MULTI_STATEMENTS, 135 | CapabilityFlags.CLIENT_MULTI_RESULTS, 136 | CapabilityFlags.CLIENT_PS_MULTI_RESULTS, 137 | CapabilityFlags.CLIENT_PLUGIN_AUTH, 138 | CapabilityFlags.CLIENT_CONNECT_ATTRS, 139 | CapabilityFlags.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA, 140 | CapabilityFlags.CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS, 141 | CapabilityFlags.CLIENT_SESSION_TRACK, 142 | CapabilityFlags.CLIENT_DEPRECATE_EOF, 143 | CapabilityFlags.UNKNOWN_30, 144 | CapabilityFlags.UNKNOWN_31 145 | ) 146 | .addAuthData(new byte[]{ 147 | 0x58, 0x2f, 0x7d, 0x63, 0x0c, 0x69, 0x6a, 0x0e, 148 | 0x6e, 0x25, 0x68, 0x3d, 0x46, 0x2e, 0x08, 0x08, 149 | 0x55, 0x77, 0x12, 0x63, 0x00 150 | }) 151 | .authPluginName(Constants.MYSQL_NATIVE_PASSWORD) 152 | .characterSet(MysqlCharacterSet.UTF8_GENERAL_CI) 153 | .connectionId(20) 154 | .addServerStatus(ServerStatusFlag.AUTO_COMMIT) 155 | .serverVersion("5.7.10") 156 | .build(); 157 | 158 | new MessageDecodeEncodeTester() 159 | .pipeline(new EmbeddedChannel( 160 | new MysqlServerConnectionPacketDecoder(), 161 | new MysqlServerPacketEncoder())) 162 | .byteBuf(captured_5_7_10_handshake()) 163 | .addMessage(handshake) 164 | .assertDecode() 165 | .assertEncode(); 166 | } 167 | 168 | private ByteBuf exampleHandshakePacket() { 169 | // Source: https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake 170 | return TestUtils.toBuf( 171 | 0x36, 0x00, 0x00, 0x00, 0x0a, 0x35, 0x2e, 0x35, 0x2e, 0x32, 0x2d, 0x6d, 0x32, 0x00, 0x0b, 0x00, 172 | 0x00, 0x00, 0x64, 0x76, 0x48, 0x40, 0x49, 0x2d, 0x43, 0x4a, 0x00, 0xff, 0xf7, 0x08, 0x02, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x34, 0x64, 174 | 0x7c, 0x63, 0x5a, 0x77, 0x6b, 0x34, 0x5e, 0x5d, 0x3a, 0x00); 175 | } 176 | 177 | private ByteBuf exampleHandshakePacketWithPluginAuth() { 178 | // Source: https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake 179 | return TestUtils.toBuf( 180 | 0x50, 0x00, 0x00, 0x00, 0x0a, 0x35, 0x2e, 0x36, 0x2e, 0x34, 0x2d, 0x6d, 0x37, 0x2d, 0x6c, 0x6f, 181 | 0x67, 0x00, 0x56, 0x0a, 0x00, 0x00, 0x52, 0x42, 0x33, 0x76, 0x7a, 0x26, 0x47, 0x72, 0x00, 0xff, 182 | 0xff, 0x08, 0x02, 0x00, 0x0f, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 183 | 0x00, 0x2b, 0x79, 0x44, 0x26, 0x2f, 0x5a, 0x5a, 0x33, 0x30, 0x35, 0x5a, 0x47, 0x00, 0x6d, 0x79, 184 | 0x73, 0x71, 0x6c, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 185 | 0x6f, 0x72, 0x64, 0x00); 186 | } 187 | 188 | private ByteBuf captured_5_7_10_handshake() { 189 | // Source: Wireshark capture 190 | return TestUtils 191 | .toBuf("4a0000000a352e372e31300014000000" + 192 | "582f7d630c696a0e00ffff210200ffc1" + 193 | "15000000000000000000006e25683d46" + 194 | "2e080855771263006d7973716c5f6e61" + 195 | "746976655f70617373776f726400"); 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /codec/src/test/java/com/github/mheath/netty/codec/mysql/integration/server/TestServer.java: -------------------------------------------------------------------------------- 1 | package com.github.mheath.netty.codec.mysql.integration.server; 2 | 3 | import java.util.ArrayList; 4 | import java.util.EnumSet; 5 | import java.util.List; 6 | import java.util.Random; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import com.github.mheath.netty.codec.mysql.ColumnCount; 11 | import com.github.mheath.netty.codec.mysql.ColumnDefinition; 12 | import com.github.mheath.netty.codec.mysql.ColumnFlag; 13 | import com.github.mheath.netty.codec.mysql.ColumnType; 14 | import com.github.mheath.netty.codec.mysql.EofResponse; 15 | import com.github.mheath.netty.codec.mysql.Handshake; 16 | import com.github.mheath.netty.codec.mysql.HandshakeResponse; 17 | import com.github.mheath.netty.codec.mysql.MysqlCharacterSet; 18 | import com.github.mheath.netty.codec.mysql.MysqlClientConnectionPacketDecoder; 19 | import com.github.mheath.netty.codec.mysql.MysqlClientPacketDecoder; 20 | import com.github.mheath.netty.codec.mysql.MysqlServerPacketEncoder; 21 | import com.github.mheath.netty.codec.mysql.QueryCommand; 22 | import com.github.mheath.netty.codec.mysql.ResultsetRow; 23 | import io.netty.bootstrap.ServerBootstrap; 24 | import io.netty.channel.Channel; 25 | import io.netty.channel.ChannelFuture; 26 | import io.netty.channel.ChannelHandlerContext; 27 | import io.netty.channel.ChannelInboundHandlerAdapter; 28 | import io.netty.channel.ChannelInitializer; 29 | import io.netty.channel.ChannelPipeline; 30 | import io.netty.channel.EventLoopGroup; 31 | import io.netty.channel.nio.NioEventLoopGroup; 32 | import io.netty.channel.socket.nio.NioServerSocketChannel; 33 | import io.netty.channel.socket.nio.NioSocketChannel; 34 | import com.github.mheath.netty.codec.mysql.CapabilityFlags; 35 | import com.github.mheath.netty.codec.mysql.MysqlClientCommandPacketDecoder; 36 | import com.github.mheath.netty.codec.mysql.OkResponse; 37 | import org.assertj.core.api.Assertions; 38 | 39 | import static org.assertj.core.api.Assertions.assertThat; 40 | 41 | /** 42 | * 43 | */ 44 | public class TestServer implements AutoCloseable { 45 | 46 | private final int port; 47 | private final String user = "user"; 48 | private final Channel channel; 49 | private final io.netty.channel.EventLoopGroup parentGroup; 50 | private final EventLoopGroup childGroup; 51 | private String password = "password"; 52 | 53 | public TestServer(int port) { 54 | this.port = port; 55 | 56 | parentGroup = new NioEventLoopGroup(); 57 | childGroup = new NioEventLoopGroup(); 58 | final ChannelFuture channelFuture = new ServerBootstrap() 59 | .group(parentGroup, childGroup) 60 | .channel(NioServerSocketChannel.class) 61 | .childHandler(new ChannelInitializer() { 62 | @Override 63 | protected void initChannel(NioSocketChannel ch) throws Exception { 64 | System.out.println("Initializing child channel"); 65 | final ChannelPipeline pipeline = ch.pipeline(); 66 | pipeline.addLast(new MysqlServerPacketEncoder()); 67 | pipeline.addLast(new MysqlClientConnectionPacketDecoder()); 68 | pipeline.addLast(new ServerHandler()); 69 | } 70 | }) 71 | .bind(port); 72 | channel = channelFuture.channel(); 73 | channelFuture.awaitUninterruptibly(); 74 | assertThat(channel.isActive()).isTrue(); 75 | System.out.println("Test MySQL server listening on port " + port); 76 | } 77 | 78 | public int getPort() { 79 | return port; 80 | } 81 | 82 | @Override 83 | public void close() { 84 | channel.close(); 85 | childGroup.shutdownGracefully().awaitUninterruptibly(); 86 | parentGroup.shutdownGracefully().awaitUninterruptibly(); 87 | } 88 | 89 | public String getPassword() { 90 | return password; 91 | } 92 | 93 | public String getUser() { 94 | return user; 95 | } 96 | 97 | private class ServerHandler extends ChannelInboundHandlerAdapter { 98 | private byte[] salt = new byte[20]; 99 | 100 | public ServerHandler() { 101 | new Random().nextBytes(salt); 102 | } 103 | 104 | @Override 105 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 106 | System.out.println("Server channel active"); 107 | final EnumSet capabilities = CapabilityFlags.getImplicitCapabilities(); 108 | CapabilityFlags.setCapabilitiesAttr(ctx.channel(), capabilities); 109 | ctx.writeAndFlush(Handshake.builder() 110 | .serverVersion("5.3.1") 111 | .connectionId(1) 112 | .addAuthData(salt) 113 | .characterSet(MysqlCharacterSet.UTF8_BIN) 114 | .addCapabilities(capabilities) 115 | .build()); 116 | } 117 | 118 | @Override 119 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 120 | System.out.println("Server channel inactive"); 121 | } 122 | 123 | @Override 124 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 125 | if (msg instanceof HandshakeResponse) { 126 | handleHandshakeResponse(ctx, (HandshakeResponse) msg); 127 | } else if (msg instanceof QueryCommand) { 128 | handleQuery(ctx, (QueryCommand) msg); 129 | } else { 130 | System.out.println("Received message: " + msg); 131 | } 132 | } 133 | 134 | @Override 135 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 136 | cause.printStackTrace(); 137 | ctx.close(); 138 | } 139 | } 140 | 141 | private void handleHandshakeResponse(ChannelHandlerContext ctx, HandshakeResponse response) { 142 | System.out.println("Received handshake response"); 143 | // TODO Validate username/password and assert database name 144 | ctx.pipeline().replace(MysqlClientPacketDecoder.class, "CommandPacketDecoder", new MysqlClientCommandPacketDecoder()); 145 | ctx.writeAndFlush(OkResponse.builder().build()); 146 | } 147 | 148 | private void handleQuery(ChannelHandlerContext ctx, QueryCommand query) { 149 | final String queryString = query.getQuery(); 150 | System.out.println("Received query: " + queryString); 151 | 152 | if (isServerSettingsQuery(queryString)) { 153 | sendSettingsResponse(ctx, query); 154 | } else { 155 | // Generic response 156 | int sequenceId = query.getSequenceId(); 157 | ctx.write(new ColumnCount(++sequenceId, 1)); 158 | ctx.write(ColumnDefinition.builder() 159 | .sequenceId(++sequenceId) 160 | .catalog("catalog") 161 | .schema("schema") 162 | .table("table") 163 | .orgTable("org_table") 164 | .name("name") 165 | .orgName("org_name") 166 | .columnLength(10) 167 | .type(ColumnType.MYSQL_TYPE_DOUBLE) 168 | .addFlags(ColumnFlag.NUM) 169 | .decimals(5) 170 | .build()); 171 | ctx.write(new EofResponse(++sequenceId, 0)); 172 | ctx.write(new ResultsetRow(++sequenceId, "1")); 173 | ctx.writeAndFlush(new EofResponse(++sequenceId, 0)); 174 | } 175 | } 176 | 177 | private boolean isServerSettingsQuery(String query) { 178 | query = query.toLowerCase(); 179 | return query.contains("select") && !query.contains("from") && query.contains("@@"); 180 | } 181 | 182 | private static Pattern SETTINGS_PATTERN = Pattern.compile("@@(\\w+)\\sAS\\s(\\w+)"); 183 | 184 | private void sendSettingsResponse(ChannelHandlerContext ctx, QueryCommand query) { 185 | final Matcher matcher = SETTINGS_PATTERN.matcher(query.getQuery()); 186 | 187 | final List values = new ArrayList<>(); 188 | int sequenceId = query.getSequenceId(); 189 | 190 | while (matcher.find()) { 191 | String systemVariable = matcher.group(1); 192 | String fieldName = matcher.group(2); 193 | switch (systemVariable) { 194 | case "character_set_client": 195 | case "character_set_connection": 196 | case "character_set_results": 197 | case "character_set_server": 198 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_VAR_STRING, 12)); 199 | values.add("utf8"); 200 | break; 201 | case "collation_server": 202 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 21)); 203 | values.add("utf8_general_ci"); 204 | break; 205 | case "init_connect": 206 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_VAR_STRING, 0)); 207 | values.add(""); 208 | break; 209 | case "interactive_timeout": 210 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_VAR_STRING, 21)); 211 | values.add("28800"); 212 | break; 213 | case "language": 214 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_VAR_STRING, 0)); 215 | values.add(""); 216 | break; 217 | case "license": 218 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_VAR_STRING, 21)); 219 | values.add("ASLv2"); 220 | break; 221 | case "lower_case_table_names": 222 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 63)); 223 | values.add("2"); 224 | break; 225 | case "max_allowed_packet": 226 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 63)); 227 | values.add("4194304"); 228 | break; 229 | case "net_buffer_length": 230 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 63)); 231 | values.add("16384"); 232 | break; 233 | case "net_write_timeout": 234 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 63)); 235 | values.add("60"); 236 | break; 237 | case "have_query_cache": 238 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 6)); 239 | values.add("YES"); 240 | break; 241 | case "sql_mode": 242 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 0)); 243 | values.add(""); 244 | break; 245 | case "system_time_zone": 246 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 6)); 247 | values.add("UTC"); 248 | break; 249 | case "time_zone": 250 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 12)); 251 | values.add("SYSTEM"); 252 | break; 253 | case "tx_isolation": 254 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 12)); 255 | values.add("REPEATABLE-READ"); 256 | break; 257 | case "wait_timeout": 258 | ctx.write(newColumnDefinition(sequenceId++, fieldName, systemVariable, ColumnType.MYSQL_TYPE_LONGLONG, 12)); 259 | values.add("28800"); 260 | break; 261 | default: 262 | throw new Error("Unknown system variable " + systemVariable); 263 | } 264 | } 265 | ctx.write(new EofResponse(++sequenceId, 0)); 266 | ctx.write(new ResultsetRow(++sequenceId, values.toArray(new String[values.size()]))); 267 | ctx.writeAndFlush(new EofResponse(++sequenceId, 0)); 268 | } 269 | 270 | private ColumnDefinition newColumnDefinition(int packetSequence, String name, String orgName, ColumnType columnType, int length) { 271 | return ColumnDefinition.builder() 272 | .sequenceId(packetSequence) 273 | .name(name) 274 | .orgName(orgName) 275 | .type(columnType) 276 | .columnLength(length) 277 | .build(); 278 | } 279 | 280 | } 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015-Present Pivotal Software Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------