/**
* 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.server;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import bftsmart.communication.SystemMessage;
import bftsmart.reconfiguration.ServerViewManager;
import bftsmart.tom.ServiceReplica;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
*
* @author alysson
*/
public class ServersCommunicationLayer extends Thread {
private ServerViewManager manager;
private LinkedBlockingQueue<SystemMessage> inQueue;
private Hashtable<Integer, ServerConnection> connections = new Hashtable<Integer, ServerConnection>();
private ServerSocket serverSocket;
private int me;
private boolean doWork = true;
private Lock connectionsLock = new ReentrantLock();
private ReentrantLock waitViewLock = new ReentrantLock();
//private Condition canConnect = waitViewLock.newCondition();
private List<PendingConnection> pendingConn = new LinkedList<PendingConnection>();
private ServiceReplica replica;
private SecretKey selfPwd;
private static final String PASSWORD = "commsyst";
public ServersCommunicationLayer(ServerViewManager manager,
LinkedBlockingQueue<SystemMessage> inQueue, ServiceReplica replica) throws Exception {
//******* EDUARDO BEGIN **************//
this.manager = manager;
this.inQueue = inQueue;
this.me = manager.getStaticConf().getProcessId();
this.replica = replica;
//Try connecting if a member of the current view. Otherwise, wait until the Join has been processed!
if (manager.isInCurrentView()) {
int[] initialV = manager.getCurrentViewAcceptors();
for (int i = 0; i < initialV.length; i++) {
if (initialV[i] != me) {
//connections.put(initialV[i], new ServerConnection(manager, null, initialV[i], inQueue));
getConnection(initialV[i]);
}
}
}
serverSocket = new ServerSocket(manager.getStaticConf().getServerToServerPort(
manager.getStaticConf().getProcessId()));
//******* EDUARDO END **************//
SecretKeyFactory fac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
PBEKeySpec spec = new PBEKeySpec(PASSWORD.toCharArray());
selfPwd = fac.generateSecret(spec);
serverSocket.setSoTimeout(10000);
serverSocket.setReuseAddress(true);
start();
}
public SecretKey getSecretKey(int id) {
if (id == manager.getStaticConf().getProcessId()) return selfPwd;
else return connections.get(id).getSecretKey();
}
//******* EDUARDO BEGIN **************//
public void updateConnections() {
connectionsLock.lock();
if (this.manager.isInCurrentView()) {
Iterator<Integer> it = this.connections.keySet().iterator();
List<Integer> toRemove = new LinkedList<Integer>();
while (it.hasNext()) {
int rm = it.next();
if (!this.manager.isCurrentViewMember(rm)) {
toRemove.add(rm);
}
}
for (int i = 0; i < toRemove.size(); i++) {
this.connections.remove(toRemove.get(i)).shutdown();
}
int[] newV = manager.getCurrentViewAcceptors();
for (int i = 0; i < newV.length; i++) {
if (newV[i] != me) {
getConnection(newV[i]);
}
}
} else {
Iterator<Integer> it = this.connections.keySet().iterator();
while (it.hasNext()) {
this.connections.get(it.next()).shutdown();
}
}
connectionsLock.unlock();
}
private ServerConnection getConnection(int remoteId) {
connectionsLock.lock();
ServerConnection ret = this.connections.get(remoteId);
if (ret == null) {
ret = new ServerConnection(manager, null, remoteId, this.inQueue, this.replica);
this.connections.put(remoteId, ret);
}
connectionsLock.unlock();
return ret;
}
//******* EDUARDO END **************//
public final void send(int[] targets, SystemMessage sm, boolean useMAC) {
ByteArrayOutputStream bOut = new ByteArrayOutputStream(248);
try {
new ObjectOutputStream(bOut).writeObject(sm);
} catch (IOException ex) {
Logger.getLogger(ServerConnection.class.getName()).log(Level.SEVERE, null, ex);
}
byte[] data = bOut.toByteArray();
for (int i : targets) {
try {
if (i == me) {
sm.authenticated = true;
inQueue.put(sm);
} else {
//System.out.println("Going to send message to: "+i);
//******* EDUARDO BEGIN **************//
//connections[i].send(data);
getConnection(i).send(data, useMAC);
//******* EDUARDO END **************//
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public void shutdown() {
doWork = false;
//******* EDUARDO BEGIN **************//
int[] activeServers = manager.getCurrentViewAcceptors();
for (int i = 0; i < activeServers.length; i++) {
//if (connections[i] != null) {
// connections[i].shutdown();
//}
if (me != activeServers[i]) {
getConnection(activeServers[i]).shutdown();
}
}
//******* EDUARDO END **************//
}
//******* EDUARDO BEGIN **************//
public void joinViewReceived() {
waitViewLock.lock();
for (int i = 0; i < pendingConn.size(); i++) {
PendingConnection pc = pendingConn.get(i);
try {
establishConnection(pc.s, pc.remoteId);
} catch (Exception e) {
e.printStackTrace();
}
}
pendingConn.clear();
waitViewLock.unlock();
}
//******* EDUARDO END **************//
@Override
public void run() {
while (doWork) {
try {
//System.out.println("Waiting for server connections");
Socket newSocket = serverSocket.accept();
ServersCommunicationLayer.setSocketOptions(newSocket);
int remoteId = new DataInputStream(newSocket.getInputStream()).readInt();
//******* EDUARDO BEGIN **************//
if (!this.manager.isInCurrentView() &&
(this.manager.getStaticConf().getTTPId() != remoteId)) {
waitViewLock.lock();
pendingConn.add(new PendingConnection(newSocket, remoteId));
waitViewLock.unlock();
} else {
establishConnection(newSocket, remoteId);
}
//******* EDUARDO END **************//
} catch (SocketTimeoutException ex) {
//timeout on the accept... do nothing
} catch (IOException ex) {
Logger.getLogger(ServersCommunicationLayer.class.getName()).log(Level.SEVERE, null, ex);
}
}
try {
serverSocket.close();
} catch (IOException ex) {
Logger.getLogger(ServersCommunicationLayer.class.getName()).log(Level.SEVERE, null, ex);
}
Logger.getLogger(ServersCommunicationLayer.class.getName()).log(Level.INFO, "Server communication layer stoped.");
}
//******* EDUARDO BEGIN **************//
private void establishConnection(Socket newSocket, int remoteId) throws IOException {
if ((this.manager.getStaticConf().getTTPId() == remoteId) || this.manager.isCurrentViewMember(remoteId)) {
connectionsLock.lock();
//System.out.println("Vai se conectar com: "+remoteId);
if (this.connections.get(remoteId) == null) { //This must never happen!!!
//first time that this connection is being established
//System.out.println("THIS DOES NOT HAPPEN....."+remoteId);
this.connections.put(remoteId, new ServerConnection(manager, newSocket, remoteId, inQueue, replica));
} else {
//reconnection
this.connections.get(remoteId).reconnect(newSocket);
}
connectionsLock.unlock();
} else {
//System.out.println("Closing connection of: "+remoteId);
newSocket.close();
}
}
//******* EDUARDO END **************//
public static void setSocketOptions(Socket socket) {
try {
socket.setTcpNoDelay(true);
} catch (SocketException ex) {
Logger.getLogger(ServersCommunicationLayer.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public String toString() {
String str = "inQueue=" + inQueue.toString();
int[] activeServers = manager.getCurrentViewAcceptors();
for (int i = 0; i < activeServers.length; i++) {
//for(int i=0; i<connections.length; i++) {
// if(connections[i] != null) {
if (me != activeServers[i]) {
str += ", connections[" + activeServers[i] + "]: outQueue=" + getConnection(activeServers[i]).outQueue;
}
}
return str;
}
//******* EDUARDO BEGIN: List entry that stores pending connections,
// as a server may accept connections only after learning the current view,
// i.e., after receiving the response to the join*************//
// This is for avoiding that the server accepts connectsion from everywhere
public class PendingConnection {
public Socket s;
public int remoteId;
public PendingConnection(Socket s, int remoteId) {
this.s = s;
this.remoteId = remoteId;
}
}
//******* EDUARDO END **************//
}