/**
* Copyright (c) 2007-2009 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags
*
* This file is part of SMaRt.
*
* SMaRt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SMaRt 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with SMaRt. If not, see <http://www.gnu.org/licenses/>.
*/
package bftsmart.communication.client.netty;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.nio.channels.ClosedChannelException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import bftsmart.communication.client.CommunicationSystemClientSide;
import bftsmart.communication.client.ReplyReceiver;
import bftsmart.reconfiguration.ClientViewManager;
import bftsmart.tom.core.messages.TOMMessage;
import bftsmart.tom.util.Logger;
import bftsmart.tom.util.TOMUtil;
/**
*
* @author Paulo
*/
@ChannelPipelineCoverage("all")
public class NettyClientServerCommunicationSystemClientSide extends SimpleChannelUpstreamHandler implements CommunicationSystemClientSide {
//private static final int MAGIC = 59;
//private static final int CONNECT_TIMEOUT = 3000;
private static final String PASSWORD = "newcs";
//private static final int BENCHMARK_PERIOD = 10000;
protected ReplyReceiver trr;
//******* EDUARDO BEGIN **************//
private ClientViewManager manager;
//******* EDUARDO END **************//
private Map sessionTable = new HashMap();
private ReentrantReadWriteLock rl;
private SecretKey authKey;
//the signature engine used in the system
private Signature signatureEngine;
//private Storage st;
//private int count = 0;
private int signatureLength;
private boolean closed = false;
public NettyClientServerCommunicationSystemClientSide(ClientViewManager manager) {
super();
try {
SecretKeyFactory fac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
PBEKeySpec spec = new PBEKeySpec(PASSWORD.toCharArray());
authKey = fac.generateSecret(spec);
this.manager = manager;
//this.st = new Storage(BENCHMARK_PERIOD);
this.rl = new ReentrantReadWriteLock();
Mac macDummy = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
signatureLength = TOMUtil.getSignatureSize(manager);
int[] currV = manager.getCurrentViewProcesses();
for (int i = 0; i < currV.length; i++) {
try {
// Configure the client.
ClientBootstrap bootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("connectTimeoutMillis", 10000);
// Set up the default event pipeline.
bootstrap.setPipelineFactory(new NettyClientPipelineFactory(this, true, sessionTable,
authKey, macDummy.getMacLength(), manager, rl, signatureLength, new ReentrantLock()));
//******* EDUARDO BEGIN **************//
// Start the connection attempt.
ChannelFuture future = bootstrap.connect(manager.getRemoteAddress(currV[i]));
//creates MAC stuff
Mac macSend = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
macSend.init(authKey);
Mac macReceive = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
macReceive.init(authKey);
NettyClientServerSession cs = new NettyClientServerSession(future.getChannel(), macSend,
macReceive, currV[i], manager.getStaticConf().getRSAPublicKey(currV[i]), new ReentrantLock());
sessionTable.put(currV[i], cs);
//System.out.println("Connecting to replica " + currV[i] + " at " + manager.getRemoteAddress(currV[i]));
//******* EDUARDO END **************//
future.awaitUninterruptibly();
if (!future.isSuccess()) {
System.err.println("Impossible to connect to " + currV[i]);
}
} catch (java.lang.NullPointerException ex) {
//What the fuck is this??? This is not possible!!!
System.err.println("Should fix the problem, and I think it has no other implications :-), "
+ "but we must make the servers store the view in a different place.");
} catch (InvalidKeyException ex) {
ex.printStackTrace(System.err);
}
}
} catch (InvalidKeySpecException ex) {
ex.printStackTrace(System.err);
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace(System.err);
}
}
@Override
public void updateConnections() {
int[] currV = manager.getCurrentViewProcesses();
try {
Mac macDummy = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
//open connections with new servers
for (int i = 0; i < currV.length; i++) {
rl.readLock().lock();
if (sessionTable.get(currV[i]) == null) {
rl.readLock().unlock();
rl.writeLock().lock();
try {
// Configure the client.
ClientBootstrap bootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("keepAlive", true);
bootstrap.setOption("connectTimeoutMillis", 10000);
// Set up the default event pipeline.
bootstrap.setPipelineFactory(
new NettyClientPipelineFactory(this, true, sessionTable,
authKey, macDummy.getMacLength(), manager, rl, signatureLength,
new ReentrantLock()));
//******* EDUARDO BEGIN **************//
// Start the connection attempt.
ChannelFuture future = bootstrap.connect(manager.getRemoteAddress(currV[i]));
//creates MAC stuff
Mac macSend = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
macSend.init(authKey);
Mac macReceive = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
macReceive.init(authKey);
NettyClientServerSession cs = new NettyClientServerSession(future.getChannel(), macSend, macReceive, currV[i], manager.getStaticConf().getRSAPublicKey(currV[i]), new ReentrantLock());
sessionTable.put(currV[i], cs);
//System.out.println("Connecting to replica " + currV[i] + " at " + manager.getRemoteAddress(currV[i]));
//******* EDUARDO END **************//
future.awaitUninterruptibly();
} catch (InvalidKeyException ex) {
ex.printStackTrace();
}
rl.writeLock().unlock();
} else {
rl.readLock().unlock();
}
}
} catch (NoSuchAlgorithmException ex) {
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
if(e.getCause() instanceof ClosedChannelException) {
System.out.println("Connection with replica closed.");
} else if(e.getCause() instanceof ConnectException) {
System.out.println("Impossible to connect to replica.");
} else {
System.out.println("Replica disconnected.");
}
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
TOMMessage sm = (TOMMessage) e.getMessage();
//delivers message to replyReceived callback
trr.replyReceived(sm);
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
//System.out.println("Channel connected");
}
@Override
public void channelClosed(
ChannelHandlerContext ctx, ChannelStateEvent e) {
if (this.closed) {
return;
}
try {
//sleeps 10 seconds before trying to reconnect
Thread.sleep(10000);
} catch (InterruptedException ex) {
}
rl.writeLock().lock();
//Iterator sessions = sessionTable.values().iterator();
ArrayList<NettyClientServerSession> sessions = new ArrayList<NettyClientServerSession>(sessionTable.values());
for (NettyClientServerSession ncss : sessions) {
if (ncss.getChannel() == ctx.getChannel()) {
try {
//******* EDUARDO BEGIN **************//
Mac macDummy = Mac.getInstance(manager.getStaticConf().getHmacAlgorithm());
// Configure the client.
ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
// Set up the default event pipeline.
bootstrap.setPipelineFactory(new NettyClientPipelineFactory(this, true, sessionTable, authKey, macDummy.getMacLength(), manager, rl, TOMUtil.getSignatureSize(manager), new ReentrantLock()));
// Start the connection attempt.
if (manager.getRemoteAddress(ncss.getReplicaId()) != null) {
ChannelFuture future = bootstrap.connect(manager.getRemoteAddress(ncss.getReplicaId()));
//******* EDUARDO END **************//
//creates MAC stuff
Mac macSend = ncss.getMacSend();
Mac macReceive = ncss.getMacReceive();
NettyClientServerSession cs = new NettyClientServerSession(future.getChannel(), macSend, macReceive, ncss.getReplicaId(), manager.getStaticConf().getRSAPublicKey(ncss.getReplicaId()), new ReentrantLock());
sessionTable.remove(ncss.getReplicaId());
sessionTable.put(ncss.getReplicaId(), cs);
//System.out.println("RE-Connecting to replica "+ncss.getReplicaId()+" at " + conf.getRemoteAddress(ncss.getReplicaId()));
} else {
// This cleans an olde server from the session table
sessionTable.remove(ncss.getReplicaId());
}
} catch (NoSuchAlgorithmException ex) {
}
}
}
//closes all other channels to avoid messages being sent to only a subset of the replicas
/*Enumeration sessionElements = sessionTable.elements();
while (sessionElements.hasMoreElements()){
((NettyClientServerSession) sessionElements.nextElement()).getChannel().close();
}*/
rl.writeLock().unlock();
}
@Override
public void setReplyReceiver(ReplyReceiver trr) {
this.trr = trr;
}
@Override
public void send(boolean sign, int[] targets, TOMMessage sm) {
if (sm.serializedMessage == null) {
//serialize message
DataOutputStream dos = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
dos = new DataOutputStream(baos);
sm.wExternal(dos);
dos.flush();
sm.serializedMessage = baos.toByteArray();
} catch (IOException ex) {
Logger.println("Impossible to serialize message: " + sm);
} finally {
try {
dos.close();
} catch (IOException ex) {
}
}
}
//Logger.println("Sending message with "+sm.serializedMessage.length+" bytes of content.");
//produce signature
if (sign && sm.serializedMessageSignature == null) {
sm.serializedMessageSignature = signMessage(
manager.getStaticConf().getRSAPrivateKey(), sm.serializedMessage);
}
int sent = 0;
for (int i = targets.length - 1; i >= 0; i--) {
sm.destination = targets[i];
rl.readLock().lock();
Channel channel = ((NettyClientServerSession) sessionTable.get(targets[i])).getChannel();
rl.readLock().unlock();
if (channel.isConnected()) {
sm.signed = sign;
channel.write(sm);
sent++;
} else {
Logger.println("Channel to " + targets[i] + " is not connected");
}
}
if (targets.length > manager.getCurrentViewF() && sent < manager.getCurrentViewF() + 1) {
//if less than f+1 servers are connected send an exception to the client
throw new RuntimeException("Impossible to connect to servers!");
}
if(targets.length == 1 && sent == 0)
throw new RuntimeException("Server not connected");
}
public void sign(TOMMessage sm) {
//serialize message
DataOutputStream dos = null;
byte[] data = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
dos = new DataOutputStream(baos);
sm.wExternal(dos);
dos.flush();
data = baos.toByteArray();
sm.serializedMessage = data;
} catch (IOException ex) {
} finally {
try {
dos.close();
} catch (IOException ex) {
}
}
//******* EDUARDO BEGIN **************//
//produce signature
byte[] data2 = signMessage(manager.getStaticConf().getRSAPrivateKey(), data);
//******* EDUARDO END **************//
sm.serializedMessageSignature = data2;
}
public byte[] signMessage(PrivateKey key, byte[] message) {
//long startTime = System.nanoTime();
try {
if (signatureEngine == null) {
signatureEngine = Signature.getInstance("SHA1withRSA");
}
byte[] result = null;
signatureEngine.initSign(key);
signatureEngine.update(message);
result = signatureEngine.sign();
//st.store(System.nanoTime() - startTime);
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void close() {
this.closed = true;
//Iterator sessions = sessionTable.values().iterator();
rl.readLock().lock();
ArrayList<NettyClientServerSession> sessions = new ArrayList<NettyClientServerSession>(sessionTable.values());
rl.readLock().unlock();
for (NettyClientServerSession ncss : sessions) {
ncss.getChannel().close();
}
}
}