View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.db.protocol.mysql.packet.handshake;
19  
20  import com.google.common.base.Preconditions;
21  import lombok.Getter;
22  import org.apache.shardingsphere.db.protocol.constant.DatabaseProtocolServerInfo;
23  import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLAuthenticationMethod;
24  import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLCapabilityFlag;
25  import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLConstants;
26  import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLStatusFlag;
27  import org.apache.shardingsphere.db.protocol.mysql.packet.MySQLPacket;
28  import org.apache.shardingsphere.db.protocol.mysql.payload.MySQLPacketPayload;
29  import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
30  import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
31  
32  /**
33   * Handshake packet protocol for MySQL.
34   * 
35   * @see <a href="https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html">Handshake</a>
36   */
37  @Getter
38  public final class MySQLHandshakePacket extends MySQLPacket {
39      
40      private final int protocolVersion = MySQLConstants.PROTOCOL_VERSION;
41      
42      private final String serverVersion;
43      
44      private final int connectionId;
45      
46      private final int capabilityFlagsLower;
47      
48      private final int characterSet;
49      
50      private final MySQLStatusFlag statusFlag;
51      
52      private final MySQLAuthenticationPluginData authPluginData;
53      
54      private int capabilityFlagsUpper;
55      
56      private String authPluginName;
57      
58      public MySQLHandshakePacket(final int connectionId, final boolean sslEnabled, final MySQLAuthenticationPluginData authPluginData) {
59          serverVersion = DatabaseProtocolServerInfo.getDefaultProtocolVersion(TypedSPILoader.getService(DatabaseType.class, "MySQL"));
60          this.connectionId = connectionId;
61          capabilityFlagsLower = MySQLCapabilityFlag.calculateHandshakeCapabilityFlagsLower() | (sslEnabled ? MySQLCapabilityFlag.CLIENT_SSL.getValue() : 0);
62          characterSet = MySQLConstants.DEFAULT_CHARSET.getId();
63          statusFlag = MySQLStatusFlag.SERVER_STATUS_AUTOCOMMIT;
64          capabilityFlagsUpper = MySQLCapabilityFlag.calculateHandshakeCapabilityFlagsUpper();
65          this.authPluginData = authPluginData;
66          authPluginName = MySQLAuthenticationMethod.CACHING_SHA2_PASSWORD.getMethodName();
67      }
68      
69      public MySQLHandshakePacket(final MySQLPacketPayload payload) {
70          Preconditions.checkArgument(protocolVersion == payload.readInt1());
71          serverVersion = payload.readStringNul();
72          connectionId = payload.readInt4();
73          final byte[] authPluginDataPart1 = payload.readStringNulByBytes();
74          capabilityFlagsLower = payload.readInt2();
75          characterSet = payload.readInt1();
76          statusFlag = MySQLStatusFlag.valueOf(payload.readInt2());
77          capabilityFlagsUpper = payload.readInt2();
78          payload.readInt1();
79          payload.skipReserved(10);
80          authPluginData = new MySQLAuthenticationPluginData(authPluginDataPart1, readAuthPluginDataPart2(payload));
81          authPluginName = readAuthPluginName(payload);
82      }
83      
84      /**
85       * There are some different between implement of handshake initialization packet and document.
86       * In source code of 5.7 version, authPluginDataPart2 should be at least 12 bytes,
87       * and then follow a nul byte.
88       * But in document, authPluginDataPart2 is at least 13 bytes, and not nul byte.
89       * From test, the 13th byte is nul byte and should be excluded from authPluginDataPart2.
90       *
91       * @param payload MySQL packet payload
92       * @return auth plugin data part2
93       */
94      private byte[] readAuthPluginDataPart2(final MySQLPacketPayload payload) {
95          return isClientSecureConnection() ? payload.readStringNulByBytes() : new byte[0];
96      }
97      
98      private String readAuthPluginName(final MySQLPacketPayload payload) {
99          return isClientPluginAuth() ? payload.readStringNul() : null;
100     }
101     
102     /**
103      * Set authentication plugin name.
104      *
105      * @param authenticationMethod MySQL authentication method
106      */
107     public void setAuthPluginName(final MySQLAuthenticationMethod authenticationMethod) {
108         authPluginName = authenticationMethod.getMethodName();
109         capabilityFlagsUpper |= MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH.getValue() >> 16;
110     }
111     
112     @Override
113     protected void write(final MySQLPacketPayload payload) {
114         payload.writeInt1(protocolVersion);
115         payload.writeStringNul(serverVersion);
116         payload.writeInt4(connectionId);
117         payload.writeStringNul(new String(authPluginData.getAuthenticationPluginDataPart1()));
118         payload.writeInt2(capabilityFlagsLower);
119         payload.writeInt1(characterSet);
120         payload.writeInt2(statusFlag.getValue());
121         payload.writeInt2(capabilityFlagsUpper);
122         payload.writeInt1(isClientPluginAuth() ? authPluginData.getAuthenticationPluginData().length + 1 : 0);
123         payload.writeReserved(10);
124         writeAuthPluginDataPart2(payload);
125         writeAuthPluginName(payload);
126     }
127     
128     private void writeAuthPluginDataPart2(final MySQLPacketPayload payload) {
129         if (isClientSecureConnection()) {
130             payload.writeStringNul(new String(authPluginData.getAuthenticationPluginDataPart2()));
131         }
132     }
133     
134     private void writeAuthPluginName(final MySQLPacketPayload payload) {
135         if (isClientPluginAuth()) {
136             payload.writeStringNul(authPluginName);
137         }
138     }
139     
140     private boolean isClientSecureConnection() {
141         return 0 != (capabilityFlagsLower & MySQLCapabilityFlag.CLIENT_SECURE_CONNECTION.getValue() & 0x00000ffff);
142     }
143     
144     private boolean isClientPluginAuth() {
145         return 0 != (capabilityFlagsUpper & MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH.getValue() >> 16);
146     }
147 }