/******************************************************************************* * 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.ServerSocket; import java.net.Socket; 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.userDatabase.User; import staticContent.framework.util.Util; public class PrevMixHandler_TCP_multiplexed_sync extends SubImplementation implements staticContent.framework.userDatabase.DatabaseEventListener { private int port; private InetAddress bindAddress; private Socket previousMixSocket; private BufferedInputStream previousMixInputStream; private BufferedOutputStream previousMixOutputStream; private AtomicBoolean replyThreadWaiting = new AtomicBoolean(false); private ServerSocket serverSocket; private int maxRequestLength; //private int queueBlockSize; private HashMap<User,Integer> thisToPrevMixIDs; private int requestBufferSize; private int replyBufferSize; private RequestThread requestThread; private ReplyThread replyThread; @Override public void constructor() { //this.queueBlockSize = settings.getPropertyAsInt("QUEUE_BLOCK_SIZE"); this.bindAddress = settings.getPropertyAsInetAddress("GLOBAL_MIX_BIND_ADDRESS"); this.port = settings.getPropertyAsInt("GLOBAL_MIX_BIND_PORT"); this.maxRequestLength = settings.getPropertyAsInt("MAX_REQUEST_LENGTH"); thisToPrevMixIDs = new HashMap<User,Integer>((int)Math.round((double)settings.getPropertyAsInt("MAX_CONNECTIONS") * 1.3d)); this.requestThread = new RequestThread(); this.replyThread = new ReplyThread(); this.requestBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REQUEST_BUFFER_SIZE"); this.replyBufferSize = settings.getPropertyAsInt("MULTIPLEXED_REPLY_BUFFER_SIZE"); } @Override public void initialize() { // TODO Auto-generated method stub } @Override public void begin() { this.requestThread.setPriority(Thread.MAX_PRIORITY); this.replyThread.setPriority(Thread.MAX_PRIORITY); this.requestThread.start(); this.replyThread.start(); } private class RequestThread extends Thread { @Override public void run() { waitForConnectionFromPreviousMix(); while (true) { try { // TODO: give blocks of messages to inputOutputHandlerInternal, not only single messages int channelIdentifier = Util.forceReadInt(previousMixInputStream); User user = userDatabase.getUser(channelIdentifier); if (user == null) { user = userDatabase.generateUser(channelIdentifier); userDatabase.addUser(user); thisToPrevMixIDs.put(user, channelIdentifier); } int messageLength = Util.forceReadInt(previousMixInputStream); if (messageLength < 1 || messageLength > maxRequestLength) { System.out.println(anonNode +" wrong size for request received"); previousMixSocket.close(); continue; } byte[] message = Util.forceRead(previousMixInputStream, messageLength); Request request = MixMessage.getInstanceRequest(message, user); //System.out.println(this +" received this message (ciphertext): " +Util.md5(request.getByteMessage())); // TODO anonNode.putInRequestInputQueue(request); } catch (IOException e) { // connection is lost System.out.println(anonNode +" connection to previous mix lost"); waitForConnectionFromPreviousMix(); continue; } } } private void waitForConnectionFromPreviousMix() { if (serverSocket == null) { try { serverSocket = new ServerSocket(port, 1, bindAddress); System.out.println(anonNode + " bound to " +bindAddress + ":" +port); } catch (IOException e) { System.out.println(anonNode + " couldn't bind socket " +bindAddress + ":" +port); e.printStackTrace(); System.exit(1); } } System.out.println(anonNode +" waiting for connection from previous mix"); while (true) { synchronized (replyThreadWaiting) { try { previousMixSocket = serverSocket.accept(); previousMixSocket.setKeepAlive(true); previousMixInputStream = new BufferedInputStream(previousMixSocket.getInputStream(), requestBufferSize); previousMixOutputStream = new BufferedOutputStream(previousMixSocket.getOutputStream(), replyBufferSize); System.out.println(anonNode +" connection established"); if (replyThreadWaiting.get()) replyThreadWaiting.notify(); break; } catch (IOException e) { System.out.println(anonNode +" connection attempt from previous mix failed"); e.printStackTrace(); try { Thread.sleep(1000); } catch (InterruptedException e1) { continue; } continue; // wait again } } } } } private class ReplyThread extends Thread { @Override public void run() { waitForConnection(); while (true) { try { Reply[] replies = anonNode.getFromReplyOutputQueue(); // TODO: add message-type-header ? for (int i=0; i<replies.length; i++) { Integer id = thisToPrevMixIDs.get(replies[i].getOwner()); if (id == null) { System.out.println(anonNode +" no id for " +replies[i].getOwner() +" available" ); continue; } //System.out.println("id: " +id +", len: " +replies[i].getByteMessage().length); //assert replies[i].getByteMessage().length > 0; assert replies[i].getByteMessage().length > 0: replies[i].getDummyStatus(); previousMixOutputStream.write(Util.intToByteArray(id)); previousMixOutputStream.write(Util.intToByteArray(replies[i].getByteMessage().length)); previousMixOutputStream.write(replies[i].getByteMessage()); previousMixOutputStream.flush(); } } catch (IOException e) { waitForConnection(); continue; } } } private void waitForConnection() { synchronized (replyThreadWaiting) { while (previousMixSocket == null || !previousMixSocket.isConnected()) { replyThreadWaiting.set(true); try { // wait for new connection replyThreadWaiting.wait(); } catch (InterruptedException e) { e.printStackTrace(); continue; } } replyThreadWaiting.set(false); } } } @Override public void userAdded(User user) { } @Override public void userRemoved(User user) { thisToPrevMixIDs.remove(user); } }