/*******************************************************************************
* 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.traceBasedTraffic;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import staticContent.evaluation.loadGenerator.ExitNodeClientData;
import staticContent.evaluation.loadGenerator.ExitNodeRequestReceiver;
import staticContent.evaluation.loadGenerator.scheduler.InOrderYieldWaitScheduler;
import staticContent.evaluation.loadGenerator.scheduler.ScheduleTarget;
import staticContent.evaluation.loadGenerator.scheduler.Scheduler;
import staticContent.evaluation.loadGenerator.scheduler.ThreadPoolScheduler;
import staticContent.evaluation.traceParser.engine.dataStructure.SendableTransaction;
import staticContent.framework.AnonNode;
import staticContent.framework.config.Settings;
import staticContent.framework.socket.socketInterfaces.StreamAnonSocketMix;
import staticContent.framework.userDatabase.User;
import staticContent.framework.util.Util;
/**
* This class represents the server-side of the RaFM-LoadGenerator.
* This means it receives the requests from the clients and sends back the
* respective replies.
*
* @author Johannes Wendel, Simon Lecheler, kpf
*
*/
public class RaFM_ExitNodeRequestReceiver extends ExitNodeRequestReceiver implements ScheduleTarget<RaFM_ReplyRecord> {
private AnonNode anonNode;
private Settings settings;
private Scheduler<RaFM_ReplyRecord> scheduler;
private Object synchronizer = new Object();
private final boolean DEBUG_ON;
protected RaFM_ExitNodeRequestReceiver(AnonNode anonNode) {
this.anonNode = anonNode;
this.settings = anonNode.getSettings();
this.DEBUG_ON = settings.getPropertyAsBoolean("AL-RaFM_DISPLAY_DEBUG_INFO");
int numberOfThreads = settings.getPropertyAsInt("AL-RaFM-NUMBER_OF_SCHEDULER_THREADS");
boolean useYieldWaitScheduler = !settings.isPropertyPresent("USE_SLOW_BUT_ACCURATE_SCHEDULER") ? false : settings.getPropertyAsBoolean("USE_SLOW_BUT_ACCURATE_SCHEDULER");
if (useYieldWaitScheduler)
this.scheduler = new InOrderYieldWaitScheduler<RaFM_ReplyRecord>(settings);
else
this.scheduler = new ThreadPoolScheduler<RaFM_ReplyRecord>(settings, numberOfThreads);
}
public static RaFM_ExitNodeRequestReceiver createInstance(AnonNode anonNode) {
return new RaFM_ExitNodeRequestReceiver(anonNode);
}
/**
* This method is called by the ApplicationLevelHandler when new data
* arrives at the server.
*/
@Override
public void dataReceived(ExitNodeClientData client, byte[] dataReceived) {
RaFM_ExitNodeClientData clientData = (RaFM_ExitNodeClientData)client;
if (anonNode.IS_DUPLEX) {
clientData.buffer = Util.concatArrays(clientData.buffer, dataReceived);
while (containsCompleteTransaction(clientData.buffer)) {
ResultSet rs = extractTransaction(clientData.buffer);
if (DEBUG_ON)
System.out.println("RaFM_ExitNodeRequestReceiver: Received Request with TA-ID " +rs.transaction.getTransactionId());
clientData.buffer = rs.leftOver;
scheduleReplies(clientData, rs.transaction);
}
}
}
private boolean containsCompleteTransaction(byte[] data) {
if (data.length < 5)
return false;
int len = Util.byteArrayToInt(Arrays.copyOfRange(data, 0, 4));
if (data.length >= len + 4)
return true;
else
return false;
}
/**
* This method tries to extract a transactions (request) from the bytes
* arrived/bypassed
*/
private ResultSet extractTransaction(byte[] data) {
assert containsCompleteTransaction(data);
ResultSet rs = new ResultSet();
int len = Util.byteArrayToInt(Arrays.copyOfRange(data, 0, 4));
rs.transaction = new SendableTransaction(Arrays.copyOfRange(data, 4, len + 4));
int leftOverLength = data.length - (len + 4);
if (leftOverLength > 0)
rs.leftOver = Arrays.copyOfRange(data, len + 4, data.length);
else
rs.leftOver = new byte[0];
return rs;
}
private class ResultSet {
byte[] leftOver;
SendableTransaction transaction;
}
/**
* This method schedules the replies for the request back to the client if
* there are any.
*/
private void scheduleReplies(RaFM_ExitNodeClientData client, SendableTransaction incomingMessage) {
if (incomingMessage.containsReplies()) {
for (int i=0; i<incomingMessage.getDistinctReplyDelays().length; i++) {
RaFM_ReplyRecord replyRecord = new RaFM_ReplyRecord(incomingMessage, client.socket.getOutputStream(), i+1);
scheduler.executeIn(TimeUnit.MILLISECONDS.toNanos(incomingMessage.getDistinctReplyDelays()[i]), this, replyRecord);
}
} else {
if (DEBUG_ON)
System.out.println("RaFM_ExitNodeRequestReceiver: No Replies contained for Client " +incomingMessage.getClientId() +" TA-ID: " +incomingMessage.getTransactionId());
}
}
/**
* This method is called by the tpScheduler and sends replies back to the
* client.
*/
@Override
public void execute(RaFM_ReplyRecord replyRecord) {
byte[] reply = replyRecord.transaction.createSendableReply();
int transactionId = replyRecord.transaction.getTransactionId();
if (DEBUG_ON)
System.out.println("RaFM_ExitNodeRequestReceiver: Sending Reply for TA-ID " + transactionId + " with length of " + reply.length + " Bytes and Send Delay of " + replyRecord.replyNumber + " ms");
try {
synchronized (synchronizer) { // TODO: try less restrictive synchronization via replyRecord.outputStream
replyRecord.outputStream.write(reply);
replyRecord.outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public ExitNodeClientData createClientDataInstance(User user, StreamAnonSocketMix socket, Object callingInstance) {
return new RaFM_ExitNodeClientData(user, socket, callingInstance);
}
public class RaFM_ExitNodeClientData extends ExitNodeClientData {
public byte[] buffer;
public RaFM_ExitNodeClientData(User user, StreamAnonSocketMix socket, Object callingInstance) {
super(user, socket, callingInstance);
this.buffer = new byte[0];
}
public RaFM_ExitNodeClientData(User user, StreamAnonSocketMix socket) {
this(user, socket, socket);
}
}
}