/*
* Copyright 2015 Julien Viet
*
* 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 io.termd.core.ssh.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.termd.core.util.Logging;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.DefaultCloseFuture;
import org.apache.sshd.common.io.IoHandler;
import org.apache.sshd.common.io.IoService;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.io.IoWriteFuture;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.common.util.closeable.AbstractCloseable;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class NettyIoSession extends AbstractCloseable implements IoSession {
private final Map<Object, Object> attributes = new HashMap<>();
private final NettyIoAcceptor acceptor;
private final IoHandler handler;
private ChannelHandlerContext context;
private SocketAddress remoteAddr;
private ChannelFuture prev;
private final DefaultCloseFuture closeFuture = new DefaultCloseFuture(null);
private final long id;
public NettyIoSession(NettyIoAcceptor acceptor, IoHandler handler) {
this.acceptor = acceptor;
this.handler = handler;
this.id = acceptor.ioService.sessionSeq.incrementAndGet();
}
final ChannelInboundHandlerAdapter adapter = new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
context = ctx;
acceptor.channelGroup.add(ctx.channel());
acceptor.ioService.sessions.put(id, NettyIoSession.this);
prev = context.newPromise().setSuccess();
remoteAddr = context.channel().remoteAddress();
acceptor.factory.handlerBridge.sessionCreated(handler, NettyIoSession.this);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
acceptor.ioService.sessions.remove(id);
acceptor.factory.handlerBridge.sessionClosed(handler, NettyIoSession.this);
context = null;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(0, bytes);
acceptor.factory.handlerBridge.messageReceived(handler, NettyIoSession.this, new ByteArrayBuffer(bytes));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
Logging.logReportedIoError(cause);
ctx.close();
}
};
public void execute(Runnable task) {
context.channel().eventLoop().execute(task);
}
public void schedule(Runnable task, long delay, TimeUnit unit) {
context.channel().eventLoop().schedule(task, delay, unit);
}
@Override
public long getId() {
return id;
}
@Override
public Object getAttribute(Object key) {
return attributes.get(key);
}
@Override
public Object setAttribute(Object key, Object value) {
return attributes.put(key, value);
}
@Override
public SocketAddress getRemoteAddress() {
return remoteAddr;
}
@Override
public SocketAddress getLocalAddress() {
return context.channel().localAddress();
}
@Override
public IoWriteFuture write(Buffer buffer) {
ByteBuf buf = Unpooled.buffer(buffer.available());
buf.writeBytes(buffer.array(), buffer.rpos(), buffer.available());
NettyIoWriteFuture msg = new NettyIoWriteFuture();
ChannelPromise next = context.newPromise();
prev.addListener(whatever -> {
if (context != null) {
context.writeAndFlush(buf, next);
}
});
prev = next;
next.addListener(fut -> {
if (fut.isSuccess()) {
msg.setValue(Boolean.TRUE);
} else {
msg.setValue(fut.cause());
}
});
return msg;
}
@Override
public IoService getService() {
return acceptor.ioService;
}
@Override
protected CloseFuture doCloseGracefully() {
context.
writeAndFlush(Unpooled.EMPTY_BUFFER).
addListener(ChannelFutureListener.CLOSE).
addListener(fut -> {
closeFuture.setClosed();
});
return closeFuture;
}
@Override
protected void doCloseImmediately() {
context.close();
super.doCloseImmediately();
}
}