/******************************************************************************* * 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.layer3outputStrategy.constantRate_v0_001; import java.util.Vector; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import staticContent.framework.controller.Implementation; import staticContent.framework.interfaces.Layer1NetworkClient; import staticContent.framework.interfaces.Layer2RecodingSchemeClient; import staticContent.framework.interfaces.Layer3OutputStrategyClient; import staticContent.framework.interfaces.Layer4TransportClient; import staticContent.framework.logger.OutputCap; import staticContent.framework.message.MixMessage; import staticContent.framework.message.Reply; import staticContent.framework.message.Request; import staticContent.framework.message.ExternalMessage.DummyStatus; import staticContent.framework.routing.MixList; import staticContent.framework.routing.RoutingMode; import staticContent.framework.util.Util; public class ClientPlugIn extends Implementation implements Layer3OutputStrategyClient, Runnable { private Layer1NetworkClient layer1; private Layer2RecodingSchemeClient layer2; private AtomicBoolean isSending = new AtomicBoolean(false); private AtomicBoolean isStopped = new AtomicBoolean(false); private int rate; // in ns private ScheduledThreadPoolExecutor scheduler; private ScheduledFuture<?> currentTimer; private ArrayBlockingQueue<byte[]> requestQueue; private static OutputCap warning; private Vector<byte[]> replyCache; private int availableReplyPayload = 0; private MixList route; private int dstPseudonym = Util.NOT_SET; @Override public void constructor() { this.rate = settings.getPropertyAsInt("CONSTANT_RATE_SEND_RATE"); this.requestQueue = new ArrayBlockingQueue<byte[]>(settings.getPropertyAsInt("CONSTANT_RATE_QUEUE_SIZE")); this.scheduler = new ScheduledThreadPoolExecutor(1); ClientPlugIn.warning = new OutputCap(this.getClass().getCanonicalName() +" cannont send messages as fast as specified (" +rate +")", 2000); } @Override public void initialize() { if (anonNode.IS_DUPLEX) this.replyCache = new Vector<byte[]>(); } @Override public void begin() { } @Override public void setReferences( Layer1NetworkClient layer1, Layer2RecodingSchemeClient layer2, Layer3OutputStrategyClient layer3, Layer4TransportClient layer4) { this.layer2 = layer2; this.layer1 = layer1; assert this == layer3; } @Override public void connect() { if (anonNode.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) this.route = sourceRoutingPlugInClient.choseRoute(); this.layer1.connect(); this.currentTimer = scheduler.scheduleAtFixedRate(this, rate, rate, TimeUnit.MILLISECONDS); this.isStopped.set(false); } @Override public void connect(int destPseudonym) { if (anonNode.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) { this.route = sourceRoutingPlugInClient.choseRoute(destPseudonym); if (anonNode.DISPLAY_ROUTE_INFO) System.out.println(""+anonNode +" generated random route: " +this.route); this.layer1.connect(this.route); } else { this.layer1.connect(); } this.currentTimer = scheduler.scheduleAtFixedRate(this, rate, rate, TimeUnit.MILLISECONDS); this.isStopped.set(false); } @Override public void disconnect() { currentTimer.cancel(false); scheduler.shutdownNow(); synchronized (this) { isStopped.set(true); } layer1.disconnect(); } @Override public void write(byte[] data) { if (data == null || data.length == 0) throw new RuntimeException("write(null) and write(byte[0]) are not allowed"); forcePutInQueue(data); } @Override public void write(byte[] data, int destPseudonym) { this.dstPseudonym = destPseudonym; write(data); } private void forcePutInQueue(byte[] data) { try { requestQueue.put(data); } catch (InterruptedException e) { e.printStackTrace(); forcePutInQueue(data); } } @Override public void run() { if (isSending.get()) warning.putOut(); isSending.set(true); synchronized (this) { if (isStopped.get() == false) { sendMessage(); isSending.set(false); } } } // TODO: might be prone to timing attacks (currently no fixed schedule for sending, but only for creating messages...) private void sendMessage() { Request request = null; if (requestQueue.size() == 0) { // send dummy request = MixMessage.getInstanceRequest(new byte[0]); if (this.dstPseudonym != Util.NOT_SET) request.destinationPseudonym = this.dstPseudonym; } else { try { request = MixMessage.getInstanceRequest(requestQueue.take()); if (this.dstPseudonym != Util.NOT_SET) request.destinationPseudonym = this.dstPseudonym; } catch (InterruptedException e) { e.printStackTrace(); sendMessage(); } } if (anonNode.ROUTING_MODE == RoutingMode.SOURCE_ROUTING) { if (!anonNode.IS_CONNECTION_BASED) { // new route for every message if (request.destinationPseudonym == Util.NOT_SET) this.route = sourceRoutingPlugInClient.choseRoute(); else this.route = sourceRoutingPlugInClient.choseRoute(request.destinationPseudonym); } request.destinationPseudonym = this.route.mixIDs[route.mixIDs.length-1]; request.nextHopAddress = this.route.mixIDs[0]; request.route = this.route.mixIDs; } request = layer2.applyLayeredEncryption(request); layer1.sendMessage(request); } @Override public byte[] receive() { if (replyCache.size() > 0) { byte[] result = replyCache.remove(0); availableReplyPayload -= result.length; return result; } else { Reply reply = layer1.receiveReply(); reply = layer2.extractPayload(reply); assert reply.getDummyStatus() != DummyStatus.UNKNOWN; while (reply.getDummyStatus() == DummyStatus.DUMMY) { reply = layer1.receiveReply(); reply = layer2.extractPayload(reply); } assert reply.getByteMessage() != null && reply.getByteMessage().length != 0; return reply.getByteMessage(); } } @Override public int getMaxSizeOfNextWrite() { return layer2.getMaxPayloadForNextMessage(); } @Override public int getMaxSizeOfNextReceive() { return layer2.getMaxPayloadForNextReply(); } @Override public int availableReplies() { tryFillCache(); return replyCache.size(); } @Override public int availableReplyData() { tryFillCache(); return availableReplyPayload; } private void tryFillCache() { for (int i=0; i<layer1.availableReplies(); i++) { Reply reply = layer1.receiveReply(); reply = layer2.extractPayload(reply); assert reply.getDummyStatus() != DummyStatus.UNKNOWN; if (reply.getDummyStatus() == DummyStatus.NO_DUMMY) { replyCache.add(reply.getByteMessage()); availableReplyPayload += reply.getByteMessage().length; assert reply.getByteMessage() != null && reply.getByteMessage().length != 0; } } } }