/*******************************************************************************
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Arrays;
import staticContent.framework.util.Util;
public class ApplicationLevelMessage {
private final static int REQUEST_HEADER_SIZE = 32;
private final static int REPLY_HEADER_SIZE = 8;
private static SecureRandom random = new SecureRandom();
private static int idCounter = 0;
private float sendDelay = Util.NOT_SET; // in sec
private int clientId = Util.NOT_SET;
private int serverId = Util.NOT_SET;
private int requestSize = Util.NOT_SET; // in byte
private int replySize = Util.NOT_SET; // in byte
private float replyDelay = Util.NOT_SET; // in sec
private int transactionId = Util.NOT_SET;
private long planedSendTime = Util.NOT_SET; // in nanosec
private long sendTime = Util.NOT_SET; // in nanosec
private long absoluteSendTime = Util.NOT_SET; // in ms; relative to 1.1.1970 UTC (use with System.currentTimeMillis())
private boolean chunkModeOn = false;
private boolean receivedAllChunks = false;
private ByteBuffer headerCache = null;
private ByteBuffer messageCache = null;
public boolean isNotifyEvent = false;
/**
* (client-side) create ApplicationLevelMessage from
* trace file
*
* @param traceFile
* @throws EndOfFileReachedException
*/
public ApplicationLevelMessage(
BufferedReader traceFile,
int transactionId
) throws EndOfFileReachedException {
String line;
try {
line = traceFile.readLine();
} catch (Exception e) {
throw new EndOfFileReachedException();
}
initThroughTraceRecordAsString(line, transactionId);
}
public ApplicationLevelMessage(String traceRecord) {
initThroughTraceRecordAsString(traceRecord);
}
/**
* (client-side) create ApplicationLevelMessage from
* the parameters specified (duplex mode)
*
* @param sendDelay
* @param clientId
* @param serverId
* @param requestSize
* @param replySize
* @param replyDelay
*/
public ApplicationLevelMessage(
float sendDelay,
int clientId,
int serverId,
int requestSize,
int replySize,
float replyDelay
) {
this.sendDelay = sendDelay;
this.clientId = clientId;
this.serverId = serverId;
this.requestSize = requestSize;
if (this.requestSize < REQUEST_HEADER_SIZE) {
//System.out.println(
// "warning: the request size specifed is too " +
// "small and will be adjusted");
this.requestSize = REQUEST_HEADER_SIZE;
}
this.replySize = replySize;
if (this.replySize < REPLY_HEADER_SIZE) {
//System.out.println(
// "warning: the reply size specifed is too " +
// "small and will be adjusted");
this.replySize = REPLY_HEADER_SIZE;
}
this.replyDelay = replyDelay;
synchronized (random) {
this.transactionId = idCounter++;
}
}
/**
* (client-side) create ApplicationLevelMessage from
* the parameters specified (simplex mode)
*
* @param sendDelay
* @param clientId
* @param serverId
* @param requestSize
*/
public ApplicationLevelMessage(
float sendDelay,
int clientId,
int serverId,
int requestSize
) {
this.sendDelay = sendDelay;
this.clientId = clientId;
this.serverId = serverId;
this.requestSize = requestSize;
if (this.requestSize < REQUEST_HEADER_SIZE) {
//System.out.println(
// "warning: the request size specifed is too " +
// "small and will be adjusted");
this.requestSize = REQUEST_HEADER_SIZE;
}
synchronized (random) {
this.transactionId = idCounter++;
}
}
/**
* (server/mix-side) create ApplicationLevelMessage
* from InputStream
*
* @param inputStream
* @throws IOException
*/
public ApplicationLevelMessage(
InputStream inputStream
) throws IOException {
// read header:
byte[] header = Util.forceRead(inputStream, REQUEST_HEADER_SIZE);
extractRequestHeaderRecords(header);
// read rest of message:
if (requestSize - REQUEST_HEADER_SIZE > 0)
Util.forceRead(inputStream, requestSize - REQUEST_HEADER_SIZE);
}
/**
* (server/mix-side) create ApplicationLevelMessage
* and add message chunks later via "addChunk(byte[] payloadChunk)"
* see "addChunk(byte[] payloadChunk)"
* see "needMore()"
* use this constructor to prevent the calling thread to block until all
* mix messages of the corresponding request are received
*/
public ApplicationLevelMessage() {
this.chunkModeOn = true;
this.headerCache = ByteBuffer.allocate(REQUEST_HEADER_SIZE);
}
public void initThroughTraceRecordAsString(String traceRecord, int transactionId) {
String[] columns = traceRecord.split("(,|;|\\s)");
if (columns.length != 6)
System.err.println("unrecognized trace file format");
this.sendDelay = Float.parseFloat(columns[0]);
this.clientId = Integer.parseInt(columns[1]);
this.serverId = Integer.parseInt(columns[2]);
this.requestSize = Integer.parseInt(columns[3]);
if (this.requestSize < REQUEST_HEADER_SIZE) {
//System.out.println(
// "warning: the request size specifed is too " +
// "small and will be adjusted");
this.requestSize = REQUEST_HEADER_SIZE;
}
this.replySize = Integer.parseInt(columns[4]);
if (this.replySize < REPLY_HEADER_SIZE) {
//System.out.println(
// "warning: the reply size specifed is too " +
// "small and will be adjusted");
this.replySize = REPLY_HEADER_SIZE;
}
this.replyDelay = Float.parseFloat(columns[5]);
this.transactionId = transactionId;
}
public void initThroughTraceRecordAsString(String traceRecord) {
synchronized (random) {
this.transactionId = idCounter++;
}
initThroughTraceRecordAsString(traceRecord, transactionId);
}
/**
* (server/mix-side) returns whether further chunks are needed or if the
* message is already received completely
* see "addChunk(byte[] payloadChunk)"
* see "ApplicationLevelMessage()"
* @return
*/
public boolean needMoreRequestChunks() {
if (!chunkModeOn)
throw new RuntimeException("only available if chunkMode is on");
return !receivedAllChunks;
}
/**
* (server/mix-side) add a message chunk
* see "ApplicationLevelMessage()"
* see "needMore()"
* @return returns null if the complete payloadChunk was required or a
* fraction of the payloadChunk otherwise
*/
public byte[] addRequestChunk(byte[] payloadChunk) {
if (!chunkModeOn)
throw new RuntimeException("cannot add chunks if constructor " +
"\"ApplicationLevelTraceEntry_RequestReply_v_001(byte[] " +
"payloadChunk)\" wasn't used to create this object"
);
// try to read header if not yet done:
if (headerCache != null) {
if (headerCache.hasRemaining()) {
if (payloadChunk.length < headerCache.remaining()) { // not enough data to read header completely
headerCache.put(payloadChunk);
return null;
} else if (payloadChunk.length == headerCache.remaining()) { // exactly enough data to read header completely
headerCache.put(payloadChunk);
extractRequestHeaderRecords(headerCache.array());
if (requestSize == REQUEST_HEADER_SIZE) {
assert headerCache.position() == requestSize;
receivedAllChunks = true;
}
return null;
} else if (payloadChunk.length > headerCache.remaining()) { // more data available than needed to read header completely
byte[][] splitted = Util.split(headerCache.remaining(), payloadChunk);
headerCache.put(splitted[0]);
extractRequestHeaderRecords(headerCache.array());
payloadChunk = splitted[1];
if (requestSize == REQUEST_HEADER_SIZE)
receivedAllChunks = true;
}
}
}
// try to read rest of message:
if (messageCache != null && messageCache.hasRemaining()) { // request not yet fully received
if (payloadChunk.length < messageCache.remaining()) { // not enough data to read message completely
messageCache.put(payloadChunk);
//System.out.println("adding chunk; transactionId,a: " +transactionId +", " +messageCache.position() +" of " +requestSize +" bytes received (" +payloadChunk.length +" bytes are new)"); // TODO
return null;
} else if (payloadChunk.length == messageCache.remaining()) { // exactly enough data to read message completely
messageCache.put(payloadChunk);
assert messageCache.position() == requestSize;
receivedAllChunks = true;
//System.out.println("request received completely: id: " +transactionId +", size: " +messageCache.position() +"bytes, overhad: 0 bytes"); // TODO
return null;
} else if (payloadChunk.length > messageCache.remaining()) { // more data available than needed to read message completely
byte[][] splitted = Util.split(messageCache.remaining(), payloadChunk);
messageCache.put(splitted[0]);
assert messageCache.position() == requestSize;
receivedAllChunks = true;
//System.out.println("request received completely: " +transactionId +", size: " +messageCache.position() +"bytes, overhad: " +splitted[1].length +" bytes"); // TODO
return splitted[1];
}
}
return null;
}
private void extractRequestHeaderRecords(byte[] byteHeader) {
if (byteHeader.length != REQUEST_HEADER_SIZE)
throw new RuntimeException("bypassed array has wrong length");
this.requestSize = Util.byteArrayToInt(Arrays.copyOfRange(byteHeader, 0, 4));
this.clientId = Util.byteArrayToInt(Arrays.copyOfRange(byteHeader, 4, 8));
this.transactionId = Util.byteArrayToInt(Arrays.copyOfRange(byteHeader, 8, 12));
this.serverId = Util.byteArrayToInt(Arrays.copyOfRange(byteHeader, 12, 16));
this.absoluteSendTime = Util.byteArrayToLong(Arrays.copyOfRange(byteHeader, 16, 24));
this.replySize = Util.byteArrayToInt(Arrays.copyOfRange(byteHeader, 24, 28));
this.replyDelay = Util.byteArrayToFloat(Arrays.copyOfRange(byteHeader, 28, 32));
if (requestSize - REQUEST_HEADER_SIZE > 0) {
this.messageCache = ByteBuffer.allocate(requestSize);
this.messageCache.put(byteHeader);
} else
this.messageCache = null;
this.headerCache = null;
//System.out.println("extracting header; transactionId: " +transactionId +", requestSize: " +requestSize); // TODO
}
/**
* (client-side) creates the payload object to be sent via the anon
* socket. the payload contains the header data needed by the server/mix
* to reply to this request
* see "ApplicationLevelTraceEntry_RequestReply_v_001()"
* see "needMore()"
* @return returns null if the complete payloadChunk was required or a
* fraction of the payloadChunk otherwise
*/
public byte[] createPayloadForRequest() {
byte[] payload = new byte[requestSize - REQUEST_HEADER_SIZE];
synchronized (random) {
random.nextBytes(payload);
}
headerCache = ByteBuffer.allocate(REPLY_HEADER_SIZE);
receivedAllChunks = false;
return Util.concatArrays(
new byte[][] {
Util.intToByteArray(requestSize),
Util.intToByteArray(clientId),
Util.intToByteArray(transactionId),
Util.intToByteArray(serverId),
Util.longToByteArray(absoluteSendTime),
Util.intToByteArray(replySize),
Util.floatToByteArray(replyDelay),
payload
});
}
/**
* (server/mix-side) create payload for reply (data to be sent via the anon
* channel)
*
* @return
*/
public byte[] createPayloadForReply() {
assert replySize != Util.NOT_SET;
assert replySize >= REPLY_HEADER_SIZE;
if (replySize == REPLY_HEADER_SIZE) {
return Util.concatArrays(new byte[][] {
Util.intToByteArray(transactionId),
Util.intToByteArray(replySize)
});
} else {
byte[] payload = new byte[replySize - REPLY_HEADER_SIZE];
synchronized (random) {
random.nextBytes(payload);
}
return Util.concatArrays(new byte[][] {
Util.intToByteArray(transactionId),
Util.intToByteArray(replySize),
payload
});
}
}
private void extractReplyHeaderRecords(byte[] header) {
if (header.length != REPLY_HEADER_SIZE)
throw new RuntimeException("bypassed array has wrong length: " +header.length +"!=" +REPLY_HEADER_SIZE);
int transactionId = Util.byteArrayToInt(Arrays.copyOfRange(header, 0, 4));
assert transactionId == this.transactionId: ""+ transactionId +"!=" +this.transactionId;
int replySize = Util.byteArrayToInt(Arrays.copyOfRange(header, 4, 8));
assert replySize == this.replySize;
if ((replySize - REPLY_HEADER_SIZE) > 0) {
this.messageCache = ByteBuffer.allocate(replySize);
this.messageCache.put(header);
} else
this.messageCache = null;
this.headerCache = null;
}
/**
* (client-side) read reply from inputStream
* blocks the calling thread until the whole reply is received (for
* none-blocking receival, the method addReplyChunk(byte[] payloadChunk)
* can be used).
*/
public void readReply(InputStream inputStream) throws IOException {
assert replySize != Util.NOT_SET;
byte[] replyHeader = Util.forceRead(inputStream, REPLY_HEADER_SIZE);
extractReplyHeaderRecords(replyHeader);
/*byte[] payload = */Util.forceRead(inputStream, replySize - REPLY_HEADER_SIZE);
}
/**
* (client-side) returns whether further chunks are needed or if the
* message is already received completely
* see "addReplyChunk(byte[] payloadChunk)"
* @return
*/
public boolean needMoreReplyChunks() {
return !receivedAllChunks;
}
/**
* (client-side) add a message chunk
* see "needMoreReplyChunks()"
* @return returns null if the complete payloadChunk was required or a
* fraction of the payloadChunk otherwise
*/
public byte[] addReplyChunk(byte[] payloadChunk) {
// try to read header if not yet done:
if (headerCache != null) {
if (headerCache.hasRemaining()) {
if (payloadChunk.length < headerCache.remaining()) { // not enough data to read header completely
headerCache.put(payloadChunk);
return null;
} else if (payloadChunk.length == headerCache.remaining()) { // exactly enough data to read header completely
headerCache.put(payloadChunk);
extractReplyHeaderRecords(headerCache.array());
if (replySize == REPLY_HEADER_SIZE) {
assert headerCache.position() == replySize;
receivedAllChunks = true;
}
return null;
} else if (payloadChunk.length > headerCache.remaining()) { // more data available than needed to read header completely
byte[][] splitted = Util.split(headerCache.remaining(), payloadChunk);
headerCache.put(splitted[0]);
extractReplyHeaderRecords(headerCache.array());
payloadChunk = splitted[1];
if (replySize == REPLY_HEADER_SIZE)
receivedAllChunks = true;
}
}
}
// try to read rest of message:
if (messageCache != null && messageCache.hasRemaining()) { // request not yet fully received
if (payloadChunk.length < messageCache.remaining()) { // not enough data to read message completely
messageCache.put(payloadChunk);
//System.err.println("added reply chunk; transactionId,a: " +transactionId +", " +messageCache.position() +" of " +messageCache.capacity() +" bytes received (" +payloadChunk.length +" bytes are new)"); // TODO
return null;
} else if (payloadChunk.length == messageCache.remaining()) { // exactly enough data to read message completely
messageCache.put(payloadChunk);
//System.err.println("added reply chunk; transactionId,b: " +transactionId +", " +messageCache.position() +" of " +messageCache.capacity() +" bytes received (" +payloadChunk.length +" bytes are new)"); // TODO
assert messageCache.position() == replySize;
receivedAllChunks = true;
//System.err.println("reply received completely: id: " +transactionId +", size: " +messageCache.position() +"bytes, overhad: 0 bytes"); // TODO
return null;
} else if (payloadChunk.length > messageCache.remaining()) { // more data available than needed to read message completely
byte[][] splitted = Util.split(messageCache.remaining(), payloadChunk);
messageCache.put(splitted[0]);
//System.err.println("added reply chunk; transactionId,c: " +transactionId +", " +messageCache.position() +" of " +messageCache.capacity() +" bytes received (" +splitted[0].length +" bytes are new)"); // TODO
assert messageCache.position() == replySize;
receivedAllChunks = true;
//System.err.println("reply received completely: " +transactionId +", size: " +messageCache.position() +"bytes, overhad: " +splitted[1].length +" bytes"); // TODO
return splitted[1];
}
}
return null;
}
public float getSendDelay() {
return sendDelay;
}
public int getSendDelayInMilliSec() {
return (int) (sendDelay*1000f);
}
public int getSendDelayInMicroSec() {
return (int) (sendDelay*1000f*1000f);
}
public long getSendDelayInNanoSec() {
return (long) (sendDelay*1000f*1000f*1000f);
}
public int getClientId() {
return clientId;
}
public int getServerId() {
return serverId;
}
public int getTransactionId() {
if (transactionId == Util.NOT_SET)
System.err.println("no transactionId set");
return transactionId;
}
public int getRequestSize() {
return requestSize;
}
public int getReplySize() {
if (replySize == Util.NOT_SET)
System.err.println("no reply size set");
return replySize;
}
public float getReplyDelay() {
if (replyDelay == Util.NOT_SET)
System.err.println("no reply delay set");
return replyDelay;
}
public int getReplyDelayInMilliSec() {
if (replyDelay == Util.NOT_SET)
System.err.println("no reply delay set");
return (int) (replyDelay*1000f);
}
public int getReplyDelayInMicroSec() {
if (replyDelay == Util.NOT_SET)
System.err.println("no reply delay set");
return (int) (replyDelay*1000f*1000f);
}
public long getReplyDelayInNanoSec() {
if (replyDelay == Util.NOT_SET)
System.err.println("no reply delay set");
return (long) (replyDelay*1000f*1000f*1000f);
}
public long getSendTime() {
return sendTime;
}
public void setSendTime(long sendTime) {
this.sendTime = sendTime;
}
public long getPlanedSendTime() {
return planedSendTime;
}
public void setPlanedSendTime(long planedSendTime) {
this.planedSendTime = planedSendTime;
}
public long getAbsoluteSendTime() {
return absoluteSendTime;
}
public void setAbsoluteSendTime(long sendTime) {
this.absoluteSendTime = sendTime;
}
@Override
public String toString() {
return serialize();
}
public String serialize() {
StringBuffer sb = new StringBuffer();
serialize(sb);
return sb.toString();
}
public StringBuffer serialize(StringBuffer bufferToAppend) {
bufferToAppend.append(sendDelay);
bufferToAppend.append(";");
bufferToAppend.append(clientId);
bufferToAppend.append(";");
bufferToAppend.append(serverId);
bufferToAppend.append(";");
bufferToAppend.append(requestSize);
bufferToAppend.append(";");
bufferToAppend.append(replySize);
bufferToAppend.append(";");
bufferToAppend.append(replyDelay);
return bufferToAppend;
}
public void serialize(Writer destination) throws IOException {
destination.write(Float.toString(sendDelay));
destination.write(";");
destination.write(Integer.toString(clientId));
destination.write(";");
destination.write(Integer.toString(serverId));
destination.write(";");
destination.write(Integer.toString(requestSize));
destination.write(";");
destination.write(Integer.toString(replySize));
destination.write(";");
destination.write(Float.toString(replyDelay));
}
}