/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.ctp.net;
import java.util.concurrent.locks.ReentrantLock;
import divconq.ctp.CtpAdapter;
import divconq.ctp.ICtpChannel;
import divconq.hub.Hub;
import divconq.log.Logger;
import divconq.net.ssl.SslHandshakeCompletionEvent;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class CtpHandler extends ChannelInboundHandlerAdapter implements ICtpChannel {
protected CtpAdapter adapter = null;
protected ChannelHandlerContext chan = null;
protected boolean serverMode = true;
protected ByteBuf remnant = null;
protected boolean readRequested = true; // initially expects a read
protected ReentrantLock readLock = new ReentrantLock();
public CtpHandler(CtpAdapter tunnel, boolean serverMode) {
this.adapter = tunnel;
this.serverMode = serverMode;
}
public void debug(String msg) {
System.out.println("Ctp " + (this.serverMode ? "Server" : "Client") + " - " + msg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
this.adapter.close();
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
this.debug("Handler added");
this.chan = ctx;
this.adapter.setChannel(this);
}
@Override
public void read() {
this.readLock.lock();
if (serverMode)
System.out.println("Start Server requested read!");
this.readRequested = false; // meaning read is covered until further notice
try {
ByteBuf rem = this.remnant;
//CtpHandler.this.debug("checking remnant: " + rem);
// if there are any unread bytes from the last read, check to see if we can collect a command
if (rem != null) {
//CtpHandler.this.debug("checking bytes: " + rem.readableBytes());
//System.out.println("Remnant ref cnt 1: " + rem.refCnt() + " for server: " + CtpHandler.this.serverMode);
boolean ready = false;
try {
ready = this.adapter.decode(rem);
}
catch (Exception x) {
// TODO error and close!!
System.out.println("Error decoding message: " + x);
return;
}
//System.out.println("Remnant ref cnt 2: " + rem.refCnt());
// if there are any unread bytes here we need to store them and combine with the next read
if (!rem.isReadable()) {
this.remnant = null;
rem.release();
}
//System.out.println("Remnant ref cnt 3: " + rem.refCnt());
if (!ready) {
this.readRequested = true;
this.chan.read();
}
else
this.adapter.handleCommand();
}
else {
this.readRequested = true;
this.chan.read();
}
if (serverMode)
System.out.println("End Server requested read!");
}
finally {
this.readLock.unlock();
}
}
@Override
public void send(ByteBuf buf, ChannelFutureListener listener) {
try {
if (this.chan != null) {
ChannelFuture future = this.chan.writeAndFlush(buf);
if (listener != null)
future.addListener(listener);
}
}
catch (Exception x) {
Logger.error("Error writing Ctp buffer: " + x);
this.close();
}
}
@Override
public void close() {
try {
if (this.chan != null)
this.chan.close().await(2000);
}
catch (InterruptedException x) {
// ignore
}
finally {
this.chan = null;
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
CtpHandler.this.readLock.lock();
try {
ByteBuf buf = (ByteBuf) msg;
if (serverMode)
System.out.println("Server got network read 1: " + buf.readableBytes());
ByteBuf rem = this.remnant;
// if there are any unread bytes from the last read, combine with this read
this.remnant = buf;
// TODO there are maybe better ways to do this - a queue of buffers?
if (rem != null) {
if (rem.isReadable()) {
this.remnant = Hub.instance.getBufferAllocator().heapBuffer(rem.readableBytes() + buf.readableBytes());
this.remnant.writeBytes(rem);
this.remnant.writeBytes(buf);
buf.release();
}
rem.release();
}
if (serverMode)
System.out.println("Server got network read 2: " + this.remnant.readableBytes());
if (!this.readRequested)
return;
if (this.remnant.readableBytes() > 256 * 1024)
System.out.println("CTP Buffer getting too large - possible issue!!!! " + this.remnant.readableBytes());
// read with the updated buffer
this.read();
}
finally {
CtpHandler.this.readLock.unlock();
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//ctx.flush();
//System.out.println("read complete");
//this.debug("Read Complete");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
this.debug("Exception");
cause.printStackTrace();
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
// on success the request first read
if (evt == SslHandshakeCompletionEvent.SUCCESS) {
// make sure auto read is off
ctx.channel().config().setAutoRead(false);
this.debug("SSL passed");
// do initial read, this is fine - just be sure streaming will work by not always reading
ctx.read();
}
}
}