/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package userGeneratedContent.testbedPlugIns.layerPlugIns.layer1network.cascade_TCP_v0_001;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import staticContent.framework.controller.SubImplementation;
import staticContent.framework.message.MixMessage;
import staticContent.framework.message.Reply;
import staticContent.framework.message.Request;
import staticContent.framework.routing.MixList;
import staticContent.framework.userDatabase.DatabaseEventListener;
import staticContent.framework.userDatabase.User;
import staticContent.framework.util.Util;
public class NextMixHandler_TCP_multiplexed_sync extends SubImplementation implements DatabaseEventListener {
private InetAddress nextMixAddress;
private int nextMixPort;
private Socket nextMixSocket; // TODO: ssl
private BufferedInputStream nextMixInputStream;
private BufferedOutputStream nextMixOutputStream;
private AtomicBoolean replyThreadWaiting = new AtomicBoolean(false);
//private int queueBlockSize;
private RequestThread requestThread;
private ReplyThread replyThread;
private int requestBufferSize;
private int replyBufferSize;
private HashMap<User,Integer> thisToNextMixIDs;
private HashMap<Integer,User> nextMixToThisIDs;
private SecureRandom random = new SecureRandom();
@Override
public void constructor() {
//this.queueBlockSize = settings.getPropertyAsInt("QUEUE_BLOCK_SIZE");
this.requestThread = new RequestThread();
this.replyThread = new ReplyThread();
this.thisToNextMixIDs = new HashMap<User,Integer>((int)Math.round((double)anonNode.EXPECTED_NUMBER_OF_USERS * 1.3d));
this.nextMixToThisIDs = new HashMap<Integer,User>((int)Math.round((double)anonNode.EXPECTED_NUMBER_OF_USERS * 1.3d));
this.requestBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REQUEST_BUFFER_SIZE");
this.replyBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REPLY_BUFFER_SIZE");
}
@Override
public void initialize() {
MixList mixList = anonNode.mixList;
if (anonNode.PUBLIC_PSEUDONYM+1 < mixList.numberOfMixes) { // TODO
this.nextMixAddress = mixList.addresses[anonNode.PUBLIC_PSEUDONYM+1];
this.nextMixPort = mixList.ports[anonNode.PUBLIC_PSEUDONYM+1];
}
userDatabase.registerEventListener(this);
}
@Override
public void begin() {
this.requestThread.setPriority(Thread.MAX_PRIORITY);
this.replyThread.setPriority(Thread.MAX_PRIORITY);
this.requestThread.connectToNextMix();
this.requestThread.start();
this.replyThread.start();
}
@Override
public void userAdded(User user) {
synchronized (nextMixToThisIDs) {
while (true) {
int idForNextMix = random.nextInt();
if (nextMixToThisIDs.get(idForNextMix) != null)
continue;
this.thisToNextMixIDs.put(user, idForNextMix);
this.nextMixToThisIDs.put(idForNextMix, user);
break;
}
}
}
@Override
public void userRemoved(User user) {
synchronized (nextMixToThisIDs) {
this.nextMixToThisIDs.remove(thisToNextMixIDs.get(user));
this.thisToNextMixIDs.remove(user);
}
}
private class RequestThread extends Thread {
@Override
public void run() {
while (true) {
Request[] requests = anonNode.getFromRequestOutputQueue();
for (int i=0; i<requests.length; i++) {
try {
synchronized (nextMixToThisIDs) {
nextMixOutputStream.write(Util.intToByteArray(thisToNextMixIDs.get(requests[i].getOwner())));
}
nextMixOutputStream.write(Util.intToByteArray(requests[i].getByteMessage().length));
nextMixOutputStream.write(requests[i].getByteMessage());
nextMixOutputStream.flush();
} catch (IOException e) {
System.out.println(anonNode +" connection to next mix (" +nextMixAddress +":" +nextMixPort +") lost");
connectToNextMix(); // reestablish connection
continue;
} catch (NullPointerException e) {
e.printStackTrace();
continue;
}
}
}
}
private void connectToNextMix() {
synchronized (replyThreadWaiting) {
System.out.println(anonNode +" trying to connect to next mix (" +nextMixAddress +":" +nextMixPort +")");
while (true) { // try to connect to next mix
try {
nextMixSocket = new Socket();
nextMixSocket.setKeepAlive(true); // permanent connection
SocketAddress receiverAddress = new InetSocketAddress(nextMixAddress, nextMixPort);
nextMixSocket.connect(receiverAddress);
nextMixOutputStream = new BufferedOutputStream(nextMixSocket.getOutputStream(), requestBufferSize);
nextMixInputStream = new BufferedInputStream(nextMixSocket.getInputStream(), replyBufferSize);
//System.out.println(".");
if (replyThreadWaiting.get() == true)
replyThreadWaiting.notify();
break; // exit loop, when no IOException has occurred
// (= connection is established successfully)
} catch (Exception e) { // connection failed
System.out.println(anonNode +" connection to next mix (" +nextMixAddress +":" +nextMixPort +") could not be established; trying to connect again");
//e.printStackTrace(); // TODO: remove
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
continue;
}
}
}
System.out.println(anonNode +" connection to next mix (" +nextMixAddress +":" +nextMixPort +") established");
}
}
}
private class ReplyThread extends Thread {
@Override
public void run() {
while (true) { // receive messages from next mix
try {
waitForConnection();
int channelIdentifier = Util.forceReadInt(nextMixInputStream);
int messageLength = Util.forceReadInt(nextMixInputStream);
if (messageLength < 1) {
System.out.println(anonNode +" wrong size for reply message received: " +messageLength);
nextMixSocket.close();
continue;
}
User user = null;
synchronized (nextMixToThisIDs) {
user = nextMixToThisIDs.get(channelIdentifier);
}
if (user == null) {
System.out.println(anonNode +" received reply from (" +nextMixAddress +":" +nextMixPort +") for an unknown channel id");
nextMixInputStream.skip(messageLength);
continue;
}
byte[] message = Util.forceRead(nextMixInputStream, messageLength);
Reply reply = MixMessage.getInstanceReply(message, user);
anonNode.putInReplyInputQueue(reply);
} catch (IOException e) {
waitForConnection();
continue;
}
}
}
private void waitForConnection() {
synchronized (replyThreadWaiting) {
while (nextMixSocket == null || !nextMixSocket.isConnected()) {
replyThreadWaiting.set(true);
try {
// wait for new connection
replyThreadWaiting.wait();
} catch (InterruptedException e) {
e.printStackTrace();
continue;
}
}
replyThreadWaiting.set(false);
}
}
}
}