package at.ac.ait.archistar.backendserver;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLEngine;
import at.ac.ait.archistar.backendserver.storageinterface.DisconnectedException;
import at.ac.ait.archistar.engine.messages.ReadCommand;
import at.ac.ait.archistar.engine.messages.WriteCommand;
import at.ac.ait.archistar.trustmanager.SSLContextFactory;
import at.archistar.bft.messages.AbstractCommand;
import at.archistar.bft.messages.CheckpointMessage;
import at.archistar.bft.messages.ClientCommand;
import at.archistar.bft.messages.IntraReplicaCommand;
import at.archistar.bft.messages.TransactionResult;
import at.archistar.bft.server.BftEngine;
import at.archistar.bft.server.BftEngineCallbacks;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.ssl.SslHandler;
/**
* This is the main archistar/bft server application logic
*
* @author andy
*/
public class OzymandiasServer implements Runnable, BftEngineCallbacks {
/**
* my internal server id
*/
private final int serverId;
/**
* a serverid -> port mapping for our servers
*/
private final Map<Integer, Integer> serverList;
/**
* upon which port is the server listening
*/
private final int port;
private final Map<Integer, ChannelHandlerContext> clientMap = new HashMap<>();
/**
* used for server-to-server communication
*/
private final ServerServerCommunication servers;
/**
* the executor is responsible for actually performing data I/O operations
* on the server
*/
private final ExecutionHandler executor;
private final EventLoopGroup bossGroup;
private final EventLoopGroup workerGroup;
/**
* this is the listening server channel (will be configured to use the
* configured server port)
*/
private ChannelFuture serverChannel;
/**
* this encapsulates the server's BFT state. It is responsible for creating
* a distributed "shared" state between all replicas/servers
*/
private final BftEngine bftEngine;
private final SecurityMonitor secMonitor;
/**
* max message size in bytes
*/
public final static int maxObjectSize = 10 * 1024 * 1024;
public OzymandiasServer(int myServerId, Map<Integer, Integer> serverList, int f, ExecutionHandler executor, NioEventLoopGroup bossGroup, NioEventLoopGroup workerGroup) {
this.bossGroup = bossGroup;
this.workerGroup = workerGroup;
this.serverList = serverList;
this.serverId = myServerId;
this.port = this.serverList.get(serverId);
this.servers = new ServerServerCommunication(serverId, this.serverList, this.workerGroup);
this.executor = executor;
this.secMonitor = new SecurityMonitor(this);
this.bftEngine = new BftEngine(serverId, f, this);
}
/**
* setup the server listening ports and handler routines
*/
@Override
@SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC_ANON")
public void run() {
/* start up netty listener */
final OzymandiasCommandHandler handler = new OzymandiasCommandHandler(this);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
SSLEngine engine = SSLContextFactory.getServerContext().createSSLEngine();
engine.setUseClientMode(false);
ch.pipeline().addLast(
new SslHandler(engine),
new ObjectEncoder(),
new ObjectDecoder(maxObjectSize, ClassResolvers.cacheDisabled(null)),
handler);
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// Bind and start to accept incoming connections.
serverChannel = b.bind(port).sync();
// wait until the server socket is closed
serverChannel.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/**
* connect to all configured BFT replicas
* @throws java.lang.InterruptedException
*/
public void connectServers() throws InterruptedException {
this.servers.connect();
}
/**
* shutdown server process (listeners and handlers
*/
public void shutdown() {
ChannelFuture cf = serverChannel.channel().close();
cf.awaitUninterruptibly();
}
/**
* return the servers id
*
* @return server replica id
*/
public int getReplicaId() {
return this.serverId;
}
/**
* this handler is called by the BFT engine as soon as a command can be
* executed upon a replica (as it was received on enough replicas in the
* same order)
*
* @param cmd the to be executed command
* @return the executed commands result
*/
@Override
public byte[] executeClientCommand(ClientCommand cmd) {
byte[] binResult = null;
try {
if (cmd instanceof WriteCommand) {
byte[] data = ((WriteCommand) cmd).getData();
binResult = this.executor.putBlob(((WriteCommand) cmd).getFragmentId(), data);
} else if (cmd instanceof ReadCommand) {
binResult = this.executor.getBlob(((ReadCommand) cmd).getFragmentId());
}
} catch (DisconnectedException ex) {
assert (false);
}
return binResult;
}
@Override
public void invalidMessageReceived(AbstractCommand msg) {
this.secMonitor.invalidMessageReceived(msg);
}
@Override
public void replicasMightBeMalicous() {
this.secMonitor.replicasMightBeMalicous();
}
@Override
public void sendToReplicas(IntraReplicaCommand cmd) {
this.servers.send(cmd);
}
@Override
public void invalidCheckpointMessage(CheckpointMessage msg) {
this.secMonitor.invalidCheckpointMessage(msg);
}
@Override
public void answerClient(TransactionResult transactionResult) {
ChannelHandlerContext ctx = this.clientMap.get(transactionResult.getClientId());
ctx.writeAndFlush(transactionResult);
}
public void setClientSession(int clientId, ChannelHandlerContext ctx) {
if (!this.clientMap.containsKey(clientId)) {
this.clientMap.put(clientId, ctx);
}
}
void processIntraReplicaCommand(IntraReplicaCommand intraReplicaCommand) {
this.bftEngine.processIntraReplicaCommand(intraReplicaCommand);
}
void processClientCommand(ClientCommand clientCommand) {
this.bftEngine.processClientCommand(clientCommand);
}
void tryAdvanceEra() {
this.bftEngine.tryAdvanceEra();
}
}