package com.tesora.dve.db.mysql.portal.protocol;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import com.tesora.dve.db.mysql.libmy.MyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
import com.tesora.dve.exceptions.PECodingException;
import com.tesora.dve.exceptions.PEException;
import org.slf4j.LoggerFactory;
public class MSPProtocolDecoder extends ChannelDuplexHandler {
private final static MSPMessage mspMessages[] = {
MSPComQueryRequestMessage.PROTOTYPE,
MSPComFieldListRequestMessage.PROTOTYPE,
MSPComQuitRequestMessage.PROTOTYPE,
MSPComSetOptionRequestMessage.PROTOTYPE,
MSPComPingRequestMessage.PROTOTYPE,
MSPComInitDBRequestMessage.PROTOTYPE,
MSPComPrepareStmtRequestMessage.PROTOTYPE,
MSPComStmtExecuteRequestMessage.PROTOTYPE,
MSPComStmtCloseRequestMessage.PROTOTYPE,
MSPComProcessInfoRequestMessage.PROTOTYPE,
MSPComStatisticsRequestMessage.PROTOTYPE
};
private final static MSPMessage[] messageMap = new MSPMessage[256];
static {
for (final MSPMessage m : mspMessages) {
messageMap[m.getMysqlMessageType()] = m;
}
}
private final MSPMessage[] messageExecutor;
private MyDecoderState currentState;
private Packet mspPacket;
public boolean firstPacket = true;
//TODO: tracking this as a single entry assumes we won't get pipelined requests from the client. -sgossard
byte nextSequence = 1;
CachedAppendBuffer cachedAppendBuffer = new CachedAppendBuffer();
private final ByteToMessageDecoder decoder = new ByteToMessageDecoder() {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
MSPProtocolDecoder.this.decode(ctx, in, out);
}
@Override
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
MSPProtocolDecoder.this.decode(ctx, in, out);
}
};
public MSPProtocolDecoder(MyDecoderState initialState) throws PEException {
super();
this.messageExecutor = messageMap;
this.currentState = initialState;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
decoder.channelInactive(ctx);
cachedAppendBuffer.releaseSlab();
if (mspPacket != null){
mspPacket.release();
mspPacket = null;
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
decoder.channelRead(ctx, msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof MyMessage) {
MyMessage message = (MyMessage)msg;
ByteBuf appendableView = cachedAppendBuffer.startAppend(ctx);
nextSequence = (byte)(0xFF & Packet.encodeFullMessage(nextSequence, message, appendableView));
ByteBuf writableSlice = cachedAppendBuffer.sliceWritableData();
ctx.write(writableSlice, promise);
} else {
//forward packet along.
ctx.write(msg, promise);
}
}
protected void decode(final ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
try {
if (mspPacket == null) {
//assumes next full inbound request will have a starting sequence (true except for auth handshake)
//also assumes we only get one request at a time (synchronous), which is typical of most mysql clients
int seq = firstPacket ? 1 : 0;
mspPacket = new Packet(ctx.alloc(), seq, Packet.Modifier.HEAPCOPY_ON_READ,"frontend");
}
//deals with the handshake packet
firstPacket = false;
if (!mspPacket.decodeMore(in))
return;
int sequenceId = mspPacket.getSequenceNumber();
this.nextSequence = (byte)(0xFF & mspPacket.getNextSequenceNumber()); //save off the sequence for our outbound response.
ByteBuf payload = mspPacket.unwrapPayload().retain();//retain a separate reference to the payload.
mspPacket.release();
mspPacket = null;
final MyDecoderState state = currentState;
switch (state) {
case READ_SERVER_GREETING:
case READ_CLIENT_AUTH: {
MSPMessage authMessage;
if (state == MyDecoderState.READ_CLIENT_AUTH) {
authMessage = MSPAuthenticateV10MessageMessage.newMessage(payload);
} else if (state == MyDecoderState.READ_SERVER_GREETING) {
authMessage = new MSPServerGreetingRequestMessage(payload);
} else {
throw new PECodingException("Unexpected state in packet decoding, " + state);
}
out.add(authMessage);
this.currentState = MyDecoderState.READ_PACKET;
break;
}
case READ_PACKET:
out.add(buildMessage(payload, (byte)sequenceId));
break;
}
} catch (Exception e){
LoggerFactory.getLogger(MSPProtocolDecoder.class).warn("problem decoding frame",e);
}finally {
}
}
private MSPMessage buildMessage(final ByteBuf payload, final byte sequenceId) {
final byte messageType = payload.getByte(0); //peek at the first byte in the payload to determine message type.
try {
return this.messageExecutor[messageType].newPrototype(payload);
} catch (final Exception e) {
return new MSPUnknown(messageType, payload);
}
}
public enum MyDecoderState {
READ_SERVER_GREETING, READ_CLIENT_AUTH, READ_PACKET
}
}