├── .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, List extends String>, 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 |
--------------------------------------------------------------------------------