/*******************************************************************************
* 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 staticContent.evaluation.loadGenerator.applicationLevelTraffic.requestReply;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import staticContent.evaluation.loadGenerator.ExitNodeClientData;
import staticContent.evaluation.loadGenerator.ExitNodeRequestReceiver;
import staticContent.evaluation.loadGenerator.LoadGenerator;
import staticContent.framework.AnonNode;
import staticContent.framework.socket.socketInterfaces.StreamAnonSocketMix;
import staticContent.framework.socket.stream.BasicOutputStreamMix;
import staticContent.framework.userDatabase.User;
import staticContent.framework.util.IOTester;
import staticContent.framework.util.Util;
public class ALRR_Scheduled_ExitNodeRequestReceiver extends ExitNodeRequestReceiver {
private AnonNode mix;
private ScheduledThreadPoolExecutor scheduler;
private ReplyThread replyThread;
private Vector<ALRR_ClientData> replyTasks;
private Vector<ALRR_ClientData> newReplyTasks;
public ALRR_Scheduled_ExitNodeRequestReceiver(AnonNode exitNode) {
this.mix = exitNode;
if (mix.IS_DUPLEX) {
this.replyTasks = new Vector<ALRR_ClientData>(1000); // TODO: dynamic
this.newReplyTasks = new Vector<ALRR_ClientData>(1000); // TODO: dynamic
this.replyThread = new ReplyThread();
this.scheduler = new ScheduledThreadPoolExecutor(4); // TODO: dynamic
this.replyThread.start();
}
}
@Override
public void dataReceived(ExitNodeClientData clientData, byte[] dataReceived) {
//System.out.println("" +this +": received (mix): " +Util.toHex(dataReceived)); // TODO: remove
ALRR_ClientData client = (ALRR_ClientData)clientData;
byte[] clone;
if (LoadGenerator.VALIDATE_IO)
clone = dataReceived.clone();
while (dataReceived != null) {
dataReceived = client.currentRequest.addRequestChunk(dataReceived);
if (client.clientId == Util.NOT_SET)
client.clientId = client.currentRequest.getClientId();
if (!client.currentRequest.needMoreRequestChunks()) { // request now received completely
// display stats:
String stats = "DISTANT_PROXY: received request (" +
"transactionId: " +client.currentRequest.getTransactionId()
+"; requestSize: " +client.currentRequest.getRequestSize() +"bytes"
+"; transfer duration: " +(System.currentTimeMillis()-client.currentRequest.getAbsoluteSendTime()) +"ms";
if (mix.IS_DUPLEX) {
stats += "; replySize: " +client.currentRequest.getReplySize() +"bytes";
stats += "; replyDelay: " +client.currentRequest.getReplyDelayInMicroSec() +"microsec";
}
stats += ")";
System.out.println(stats);
// schedule reply if duplex:
if (mix.IS_DUPLEX) {
//System.err.println("schedule for in " +client.currentRequest.getReplyDelayInMilliSec() +"ms"); // TODO: remove
scheduler.schedule(
new SendReplyTask(client, client.currentRequest),
client.currentRequest.getReplyDelayInMicroSec(),
TimeUnit.MICROSECONDS
);
}
client.currentRequest = new ApplicationLevelMessage();
}
}
if (LoadGenerator.VALIDATE_IO) {
IOTester tester = IOTester.findInstance(""+client.clientId);
tester.addReceiveRecord(clone);
}
}
@Override
public ExitNodeClientData createClientDataInstance(User user, StreamAnonSocketMix socket, Object callingInstance) {
return new ALRR_ClientData(user, socket, callingInstance);
}
private class ALRR_ClientData extends ExitNodeClientData {
ApplicationLevelMessage currentRequest = new ApplicationLevelMessage();
BasicOutputStreamMix outputStream;
private ConcurrentLinkedQueue<ApplicationLevelMessage> replyTasks;
private AtomicInteger availableReplyData = new AtomicInteger(0);
private ByteBuffer leftOver;
public ALRR_ClientData(User user, StreamAnonSocketMix socket, Object callingInstance) {
super(user, socket, callingInstance);
if (mix.IS_DUPLEX) {
this.outputStream = (BasicOutputStreamMix)socket.getOutputStream();
this.replyTasks = new ConcurrentLinkedQueue<ApplicationLevelMessage>();
this.leftOver = ByteBuffer.allocate(0);
this.leftOver.flip();
}
}
public void addReplyTask(ApplicationLevelMessage replyTask) {
replyTasks.add(replyTask);
availableReplyData.addAndGet(replyTask.getReplySize());
}
public byte[] getReplyData(int amount) {
if (amount > availableReplyData.get())
System.err.println("not enough data available");
else if (amount == 0)
return null;
byte[] result = new byte[amount];
if (leftOver.remaining() >= amount) {
leftOver.get(result);
} else { // leftOver.remaining() < amount
ByteBuffer resultBuffer = ByteBuffer.wrap(result);
if (leftOver.hasRemaining()) {
resultBuffer.put(leftOver);
}
while(resultBuffer.hasRemaining()) {
ApplicationLevelMessage nextEntry;
nextEntry = replyTasks.remove();
byte[] payload = nextEntry.createPayloadForReply();
String stats = "DISTANT_PROXY: sending reply (" +
"transactionId: " +nextEntry.getTransactionId()
+ "; replySize: " +nextEntry.getReplySize() +"bytes"
+ "; replyDelay: " +nextEntry.getReplyDelayInMilliSec() +"ms"
+ ")";
System.out.println(stats);
//System.err.println("mix sending reply (" +payload.length +" bytes): " +Arrays.toString(payload)); // TODO: remove
if (payload.length <= resultBuffer.remaining()) {
resultBuffer.put(payload);
} else { // more data than needed
byte[][] splitted = Util.split(resultBuffer.remaining(), payload);
resultBuffer.put(splitted[0]);
leftOver = ByteBuffer.wrap(splitted[1]);
}
}
}
availableReplyData.set(availableReplyData.get() - result.length);
return result;
}
public int availableReplyData() {
return availableReplyData.get();
}
}
public class SendReplyTask implements Runnable {
private ALRR_ClientData clientData;
ApplicationLevelMessage replyTask;
public SendReplyTask(ALRR_ClientData clientData, ApplicationLevelMessage replyTask) {
this.clientData = clientData;
this.replyTask = replyTask;
}
@Override
public void run() {
//System.err.println("execute"); // TODO: remove
synchronized (newReplyTasks) {
clientData.addReplyTask(replyTask);
newReplyTasks.add(clientData);
}
}
}
private class ReplyThread extends Thread {
@Override
public void run() {
while (true) {
// add new reply tasks
synchronized(newReplyTasks) {
for (ALRR_ClientData replyTask: newReplyTasks)
replyTasks.add(replyTask);
}
// try to write replies
int writeCtr = 0;
ALRR_ClientData replyTask;
for (int i=0; i<replyTasks.size(); i++) {
replyTask = replyTasks.elementAt(i);
try {
// wait for free solt:
int tries = 0;
while (mix.getReplyInputQueue().remainingCapacity() == 0) {
if (tries++ < 10)
Thread.yield();
else
try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();continue;} // TODO: wait-notify?
}
int payloadSize = Math.min(replyTask.outputStream.getMTU(), replyTask.availableReplyData());
if (payloadSize > 0) {
byte[] payload = replyTask.getReplyData(payloadSize);
if (LoadGenerator.VALIDATE_IO)
IOTester.findInstance("reply-"+replyTask.clientId).addSendRecord(payload);
//System.out.println("" +this +": sending (reply (mix)): " +Util.toHex(payload)); // TODO: remove
replyTask.socket.getOutputStream().write(payload);
replyTask.socket.getOutputStream().flush();
writeCtr++;
}
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
if (writeCtr == 0)
try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
}
}
}
}