/*
* Copyright 2012-2015 org.opencloudb.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opencloudb.heartbeat;
import java.nio.channels.SocketChannel;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import org.opencloudb.config.Capabilities;
import org.opencloudb.config.ErrorCode;
import org.opencloudb.mysql.SecurityUtil;
import org.opencloudb.net.BackendConnection;
import org.opencloudb.net.mysql.AuthPacket;
import org.opencloudb.net.mysql.CommandPacket;
import org.opencloudb.net.mysql.HandshakePacket;
import org.opencloudb.net.mysql.MySQLPacket;
import org.opencloudb.net.mysql.QuitPacket;
import org.opencloudb.util.TimeUtil;
/**
* @author mycat
*/
public class MySQLDetector extends BackendConnection {
private static final Logger LOGGER = Logger.getLogger(MySQLDetector.class);
private static final long CLIENT_FLAGS = initClientFlags();
private MySQLHeartbeat heartbeat;
private final long clientFlags;
private HandshakePacket handshake;
private int charsetIndex;
private boolean isAuthenticated;
private String user;
private String password;
private String schema;
private long heartbeatTimeout;
private final AtomicBoolean isQuit;
public MySQLDetector(SocketChannel channel) {
super(channel);
this.clientFlags = CLIENT_FLAGS;
this.handler = new MySQLDetectorAuthenticator(this);
this.isQuit = new AtomicBoolean(false);
}
public MySQLHeartbeat getHeartbeat() {
return heartbeat;
}
public void setHeartbeat(MySQLHeartbeat heartbeat) {
this.heartbeat = heartbeat;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getHeartbeatTimeout() {
return heartbeatTimeout;
}
public void setHeartbeatTimeout(long heartbeatTimeout) {
this.heartbeatTimeout = heartbeatTimeout;
}
public boolean isHeartbeatTimeout() {
return TimeUtil.currentTimeMillis() > Math.max(lastWriteTime,
lastReadTime) + heartbeatTimeout;
}
public long lastReadTime() {
return lastReadTime;
}
public long lastWriteTime() {
return lastWriteTime;
}
public boolean isAuthenticated() {
return isAuthenticated;
}
public void setAuthenticated(boolean isAuthenticated) {
this.isAuthenticated = isAuthenticated;
}
public HandshakePacket getHandshake() {
return handshake;
}
public void setHandshake(HandshakePacket handshake) {
this.handshake = handshake;
}
public void setCharsetIndex(int charsetIndex) {
this.charsetIndex = charsetIndex;
}
public void authenticate() {
AuthPacket packet = new AuthPacket();
packet.packetId = 1;
packet.clientFlags = clientFlags;
packet.maxPacketSize = maxPacketSize;
packet.charsetIndex = charsetIndex;
packet.user = user;
try {
packet.password = getPass(password, handshake);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage());
}
packet.database = schema;
packet.write(this);
}
public void heartbeat() {
if (isAuthenticated) {
String sql = heartbeat.getHeartbeatSQL();
if (sql != null) {
CommandPacket packet = new CommandPacket();
packet.packetId = 0;
packet.command = MySQLPacket.COM_QUERY;
packet.arg = sql.getBytes();
packet.write(this);
}
} else {
// System.out.println("auth ");
authenticate();
}
}
public void quit() {
if (isQuit.compareAndSet(false, true)) {
if (isAuthenticated) {
write(writeToBuffer(QuitPacket.QUIT, allocate()));
write(processor.getBufferPool().allocate());
} else {
close("heartbeat quit");
}
}
}
public boolean isQuit() {
return isQuit.get();
}
@Override
public void error(int errCode, Throwable t) {
LOGGER.warn(toString(), t);
switch (errCode) {
case ErrorCode.ERR_HANDLE_DATA:
heartbeat.setResult(MySQLHeartbeat.ERROR_STATUS, this, false);
break;
default:
heartbeat.setResult(MySQLHeartbeat.ERROR_STATUS, this, true);
}
}
@Override
protected void idleCheck() {
if (isIdleTimeout()) {
LOGGER.warn(toString() + " idle timeout");
quit();
}
}
public String toString() {
return new StringBuilder().append("[thread=")
.append(Thread.currentThread().getName()).append(",class=")
.append(getClass().getSimpleName()).append(",host=")
.append(host).append(",port=").append(port)
.append(",localPort=").append(localPort).append(",schema=")
.append(schema).append(']').toString();
}
private static long initClientFlags() {
int flag = 0;
flag |= Capabilities.CLIENT_LONG_PASSWORD;
flag |= Capabilities.CLIENT_FOUND_ROWS;
flag |= Capabilities.CLIENT_LONG_FLAG;
flag |= Capabilities.CLIENT_CONNECT_WITH_DB;
// flag |= Capabilities.CLIENT_NO_SCHEMA;
// flag |= Capabilities.CLIENT_COMPRESS;
flag |= Capabilities.CLIENT_ODBC;
// flag |= Capabilities.CLIENT_LOCAL_FILES;
flag |= Capabilities.CLIENT_IGNORE_SPACE;
flag |= Capabilities.CLIENT_PROTOCOL_41;
flag |= Capabilities.CLIENT_INTERACTIVE;
// flag |= Capabilities.CLIENT_SSL;
flag |= Capabilities.CLIENT_IGNORE_SIGPIPE;
flag |= Capabilities.CLIENT_TRANSACTIONS;
// flag |= Capabilities.CLIENT_RESERVED;
flag |= Capabilities.CLIENT_SECURE_CONNECTION;
// client extension
// flag |= Capabilities.CLIENT_MULTI_STATEMENTS;
// flag |= Capabilities.CLIENT_MULTI_RESULTS;
return flag;
}
private static byte[] getPass(String src, HandshakePacket hsp)
throws NoSuchAlgorithmException {
if (src == null || src.length() == 0) {
return null;
}
byte[] passwd = src.getBytes();
int sl1 = hsp.seed.length;
int sl2 = hsp.restOfScrambleBuff.length;
byte[] seed = new byte[sl1 + sl2];
System.arraycopy(hsp.seed, 0, seed, 0, sl1);
System.arraycopy(hsp.restOfScrambleBuff, 0, seed, sl1, sl2);
return SecurityUtil.scramble411(passwd, seed);
}
}