/******************************************************************************* * 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.traceParser.engine.dataStructure; import java.io.BufferedReader; import java.io.IOException; import java.io.Writer; import java.security.SecureRandom; import staticContent.framework.util.FragmentedMessage; import staticContent.framework.util.Util; /** * A Transaction consists of exactly one request and zero or more replies (cf. * [1]). This data structure (class) can be used to replay Transactions in * simulation or emulation setups. It contains data required on client- and * server-side to determine the size of and delay between requests and replies. * * supported modes (cf. [1]): * MODE I: SIMPLEX_OPEN_LOOP (for SIMPLEX simulation scenarios WITH UNbounded * communication links (unlimited bandwidth; 0 latency)) * MODE II: SIMPLEX_WITH_FEEDBACK (for SIMPLEX simulations that takes feedback * from the communication links into account, e.g. TCP congestion * control effects etc. (in fact anything else than unlimited * bandwidth and 0 latency)) * MODE III: DUPLEX (for DUPLEX simulations that takes feedback * from the communication links into account, e.g. TCP congestion * control effects etc.) * * [1] Karl-Peter Fuchs, Dominik Herrmann, and Hannes Federrath: "Generating * Realistic Application Workloads for Mix-Based Systems for Controllable, * Repeatable and Usable Experimentation", IFIP SEC 2013 */ public class Transaction { protected static SecureRandom random = new SecureRandom(); /** * counter for the transactionId variable */ protected static int idCounter = 0; /** unique id for this transaction */ protected int transactionId = Util.NOT_SET; /** id of the server who shall perform this transaction */ protected int serverId = Util.NOT_SET; /** * client shall start this transaction (i.e. send the request (see below)) * after "sendDelay" ms. * Note: this value is referred to as "user think time" in many papers. * Note: this variable corresponds to "T1-T0" in Fig.5 of [1] (or "T13-T12", * if we assume that this is the second transaction of Fig.5 in [1]) * * Note: this variable is relevant for all modes * Note: used on client-side only */ protected int sendDelay = Util.NOT_SET; /** * size of the request (i.e. the message from the client to the server) * in byte (0 if server shall send first message) * * Note: this variable corresponds to "A_1" and "A_2" in Fig.3 of [1] * Note: this variable is relevant for all modes * Note: used on client-side only */ protected int requestSize = Util.NOT_SET; /** * size of all reply messages in total (0 if no message from server to * client shall be sent) * * Note: this variable corresponds to "B_1 + B_2" in Fig.3 of [1] * Note (informational): this variable is the sum of all individual sizes * stored in the "distinctReplySizes"-array below * * Note: this variable is relevant for DUPLEX-Mode only * Note: used on server/mix-side only */ protected int totalReplySize = Util.NOT_SET; /** * delta between the p.o.t. (point of time) the first packet of the request * left the client and the p.o.t. the last packet of the reply was received * by the client in the original trace in ms. 0 if no reply contained. * * Note: this variable corresponds to "T12-T1" in Fig.5 of [1] * * Note: this variable is relevant for SIMPLEX_OPEN_LOOP-Mode only * * Note: used on client-side only * * how to use: * 1. wait "sendDelay" ms * 2. send request and at the same (simulation) time wait * "simplexReplyDelay" ms * 3. load the next transaction and repeat with 1. */ protected int simplexReplyDelay = Util.NOT_SET; /** * array containing the sizes of each reply of this transaction. the size * of the first reply is found at index 0 of the array. array is null if * this transaction does not contain any replies. * * see comment for "distinctReplySizes" below on how to use this array * * Note: this array corresponds to "B_1" and B_2" in Fig.3 of [1], i.e., * distinctReplySizes[0] = "B_1" and distinctReplySizes[1] = "B_2". * * Note: this variable is relevant for DUPLEX-Mode only * Note: used on server/mix-side only */ protected int[] distinctReplySizes = null; /** * array containing the delays (in ms) after which each reply of this * transaction shall be sent by the last mix (right after the last mix has * fully received the request of this transaction). * the delays are supposed to assure that the simulated server doesn't * reply faster than the original server did (based on observations * extracted from the trace file this transaction was created from * * Note: uses the same index as the "distinctReplySizes"-array above * Note: distinctReplyDelays[0] corresponds to "T6-T1" in Fig.5 of [1] and * distinctReplyDelays[1] corresponds to "T10-T1" in Fig.5 of [1] * * Note: this variable is relevant for DUPLEX-Mode only * Note: used on server/mix-side only * * how to use: * 1. wait until the request of this transaction is fully received * at the last mix (= T1) * 2. schedule each reply of this message for later sending (without * waiting): * for (int i=0; i<distinctReplyDelays.length; i++) * scheduler.sendReplyIn(distinctReplyDelays[i], * distinctReplySizes[i]); */ protected int[] distinctReplyDelays = null; /** * array containing the delays (in ms) after which the arrival of replies * should be simulated in SIMPLEX_WITH_FEEDBACK-Mode. * * Note: uses the same index as the "distinctReplySizes"-array above * Note: distinctReplyDelays[0] corresponds to "T8-T1" in Fig.5 of [1] and * distinctReplyDelays[1] corresponds to "T12-T8" in Fig.5 of [1] * * Note: this variable is relevant for SIMPLEX_WITH_FEEDBACK-Mode only * Note: used on clinet-side only * * how to use: * 1. wait "sendDelay" ms * 2. send request and wait until it is fully sent, i.e., wait until * the write()-method returns (note: we assume a blocking socket * here) * 3. schedule the arrival of the next reply (not all replies!): * scheduler.simulateReplyArrivalIn(distinctSimplexWithFeedbackReplyDelays[0]); * 4. on arrival of the reply (that was scheduled in 3.): * schedule next reply, i.e. GOTO 3.: * (scheduler.simulateReplyArrivalIn(distinctSimplexWithFeedbackReplyDelays[i++]);) * */ protected int[] distinctSimplexWithFeedbackReplyDelays = null; /** * for statistics. set when messages is sent via load generator or * simulator; in ms; offset from 1.1.1970 UTC for load generator (use * with System.currentTimeMillis()); for simulator: offset from start of * simulation (Simulator.getNow()). */ protected long timestampSend = Util.NOT_SET; // public Object attachment; /** * See class comment. */ public Transaction() { } /** * See class comment. */ public Transaction( int transactionId, int sendDelay, int serverId, int requestSize, int[] replySizes, int[] replyDelays ) { this.transactionId = transactionId; this.sendDelay = sendDelay; this.serverId = serverId; this.requestSize = requestSize; this.distinctReplySizes = replySizes; this.distinctReplyDelays = replyDelays; } /** * See class comment. */ public Transaction( int transactionId, int sendDelay, int serverId, int requestSize ) { this( transactionId, sendDelay, serverId, requestSize, null, null ); } /** * See class comment. */ public Transaction(String serializedTransaction) { String[] fields = serializedTransaction.split("(,|;|\\s)"); if (fields.length < 5) throw new RuntimeException("unknown format (not a serialized Transaction): " +serializedTransaction); assert (fields.length % 2) == 1; this.transactionId = idCounter++; this.sendDelay = Integer.parseInt(fields[0]); //this.clientId = Integer.parseInt(fields[1]); this.serverId = Integer.parseInt(fields[1]); this.requestSize = Integer.parseInt(fields[2]); this.totalReplySize = Integer.parseInt(fields[3]); this.simplexReplyDelay = Integer.parseInt(fields[4]); int replyRecords = (fields.length - 5) / 2; if (replyRecords > 0) { this.distinctReplySizes = new int[replyRecords]; this.distinctReplyDelays = new int[replyRecords]; for (int i=0; i<replyRecords; i++) { this.distinctReplySizes[i] = Integer.parseInt(fields[5+i*2]); this.distinctReplyDelays[i] = Integer.parseInt(fields[6+i*2]); } } } /** * See class comment. */ public static Transaction readTransaction(BufferedReader trace) throws IOException { String line = trace.readLine(); return new Transaction(line); } /** * delta between the p.o.t. (point of time) the first packet of the request * left the client and the p.o.t. the last packet of the reply was received * by the client in the original trace in ms. 0 if no reply contained. * * Note: this variable corresponds to "T12-T1" in Fig.5 of [1] * * Note: this variable is relevant for SIMPLEX_OPEN_LOOP-Mode only * * Note: used on client-side only * * how to use: * 1. wait "sendDelay" ms * 2. send request and at the same (simulation) time wait * "simplexReplyDelay" ms * 3. load the next transaction and repeat with 1. */ public int getSimplexReplyDelay() { return simplexReplyDelay; } // TODO: public void reuse() ? // TODO: public static Transaction readTransaction(BufferedReader trace, Packet reusePacket) throws IOException { @Override public String toString() { return "transaction "+transactionId +": " +serialize(); } public String serialize() { StringBuffer sb = new StringBuffer(); serialize(sb); return sb.toString(); } public StringBuffer serialize(StringBuffer bufferToAppend) { assert totalReplySize != Util.NOT_SET; bufferToAppend.append(Integer.toString(sendDelay)); bufferToAppend.append(";"); //bufferToAppend.append(Integer.toString(clientId)); //bufferToAppend.append(";"); bufferToAppend.append(Integer.toString(serverId)); bufferToAppend.append(";"); bufferToAppend.append(Integer.toString(requestSize)); bufferToAppend.append(";"); bufferToAppend.append(Integer.toString(totalReplySize)); bufferToAppend.append(";"); bufferToAppend.append(Integer.toString(simplexReplyDelay)); if (distinctReplySizes != null && distinctReplySizes.length != 0) for (int i=0; i<distinctReplySizes.length; i++) { bufferToAppend.append(";"); bufferToAppend.append(Integer.toString(distinctReplySizes[i])); bufferToAppend.append(";"); bufferToAppend.append(Integer.toString(distinctReplyDelays[i])); } return bufferToAppend; } public void serialize(Writer destination) throws IOException { assert totalReplySize != Util.NOT_SET; destination.write(Integer.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(totalReplySize)); destination.write(";"); destination.write(Integer.toString(simplexReplyDelay)); if (containsReplies()) for (int i=0; i<distinctReplySizes.length; i++) { destination.write(";"); destination.write(Integer.toString(distinctReplySizes[i])); destination.write(";"); destination.write(Integer.toString(distinctReplyDelays[i])); } } /** * client shall start this transaction (i.e. send the request (see below)) * after RETURN ms. * Note: this value is referred to as "user think time" in many papers. * Note: this variable corresponds to "T1-T0" in Fig.5 of [1] (or "T13-T12", * if we assume that this is the second transaction of Fig.5 in [1]) * * Note: this variable is relevant for all modes * Note: used on client-side only */ public int getSendDelay() { return sendDelay; } /** id of the server who shall perform this transaction */ public int getServerId() { return serverId; } /** unique id for this transaction */ public int getTransactionId() { return transactionId; } /** * size of the request (i.e. the message from the client to the server) * in byte (0 if server shall send first message) * * Note: this variable corresponds to "A_1" and "A_2" in Fig.3 of [1] * Note: this variable is relevant for all modes * Note: used on client-side only */ public int getRequestSize() { return requestSize; } public void setRequestSize(int newSize) { this.requestSize = newSize; } /** * size of all reply messages in total (0 if no message from server to * client shall be sent) * * Note: this variable corresponds to "B_1 + B_2" in Fig.3 of [1] * Note (informational): this variable is the sum of all individual sizes * stored in the "distinctReplySizes"-array below * * Note: this variable is relevant for DUPLEX-Mode only * Note: used on server/mix-side only */ public int getTotalReplySize() { return totalReplySize; } /** * for statistics. set when messages is sent via load generator or * simulator; in ms; relative to 1.1.1970 UTC for load generator (use * with System.currentTimeMillis()); for simulator: relative to start of * simulation (Simulator.getNow()). */ public long getTimestampSend() { return timestampSend; } /** * for statistics. set when messages is sent via load generator or * simulator; in ms; relative to 1.1.1970 UTC for load generator (use * with System.currentTimeMillis()); for simulator: relative to start of * simulation (Simulator.getNow()). */ public void setTimestampSend(long sendTime) { this.timestampSend = sendTime; } /** * array containing the sizes of each reply of this transaction. the size * of the first reply is found at index 0 of the array. array is null if * this transaction does not contain any replies. * * see comment for "distinctReplySizes" below on how to use this array * * Note: this array corresponds to "B_1" and B_2" in Fig.3 of [1], i.e., * distinctReplySizes[0] = "B_1" and distinctReplySizes[1] = "B_2". * * Note: this variable is relevant for DUPLEX-Mode only * Note: used on server/mix-side only */ public int[] getDistinctReplySizes() { return distinctReplySizes; } public void setDistinctReplySizes(int[] distinctReplySizes) { this.distinctReplySizes = distinctReplySizes; } public boolean containsReplies() { return !(distinctReplySizes == null || distinctReplySizes.length == 0 || distinctReplySizes[0] == 0); } /** * array containing the delays (in ms) after which each reply of this * transaction shall be sent by the last mix (right after the last mix has * fully received the request of this transaction). * the delays are supposed to assure that the simulated server doesn't * reply faster than the original server did (based on observations * extracted from the trace file this transaction was created from * * Note: uses the same index as the "distinctReplySizes"-array above * Note: distinctReplyDelays[0] corresponds to "T6-T1" in Fig.5 of [1] and * distinctReplyDelays[1] corresponds to "T10-T1" in Fig.5 of [1] * * Note: this variable is relevant for DUPLEX-Mode only * Note: used on server/mix-side only * * how to use: * 1. wait until the request of this transaction is fully received * at the last mix (= T1) * 2. schedule each reply of this message for later sending (without * waiting): * for (int i=0; i<distinctReplyDelays.length; i++) * scheduler.sendReplyIn(distinctReplyDelays[i], * distinctReplySizes[i]); */ public int[] getDistinctReplyDelays() { return distinctReplyDelays; } //public int[] getDistinctSimplexWithFeedbackReplyDelays() { // return distinctSimplexWithFeedbackReplyDelays; //} int distinctSimplexWithFeedbackReplyDelayIndex = -1; /** * returns the delays (in ms) after which the arrival of replies * should be simulated in SIMPLEX_WITH_FEEDBACK-Mode. * * Note: the value returned by this method after the first call corresponds * to "T8-T1" in Fig.5 of [1] and the second to "T12-T8" * * Note: this method is relevant for SIMPLEX_WITH_FEEDBACK-Mode only * Note: used on clinet-side only * * how to use: * 1. wait "sendDelay" ms * 2. send request and wait until it is fully sent, i.e., wait until * the write()-method returns (note: we assume a blocking socket * here) * 3. schedule the arrival of the next reply (not all replies!): * scheduler.simulateReplyArrivalIn(getNextDistinctSimplexWithFeedbackReplyDelay()); * 4. on arrival of the reply (that was scheduled in 3.): * schedule next reply, i.e. GOTO 3.: * (scheduler.simulateReplyArrivalIn(getNextDistinctSimplexWithFeedbackReplyDelay());) * Use hasMoreDistinctSimplexWithFeedbackReplyDelays() to check, * whether further replies are present. * */ public int getNextDistinctSimplexWithFeedbackReplyDelay() { distinctSimplexWithFeedbackReplyDelayIndex++; return distinctSimplexWithFeedbackReplyDelays[distinctSimplexWithFeedbackReplyDelayIndex]; } public boolean hasMoreDistinctSimplexWithFeedbackReplyDelays() { if (!containsReplies()) return false; return distinctSimplexWithFeedbackReplyDelayIndex < (distinctSimplexWithFeedbackReplyDelays.length - 1); } /** * (client-side) creates the payload object (request) to be sent via the * anon socket. the request contains the header data needed by the * server/last mix to reply to this request (finish the transaction) */ public byte[] createSendableTransaction(int clientId) { short replyFields = (!containsReplies()) ? 0 : (short) distinctReplySizes.length; byte[] result = Util.concatArrays(new byte[][] { Util.intToByteArray(transactionId), Util.shortToByteArray(replyFields), Util.longToByteArray(timestampSend), Util.intToByteArray(clientId), Util.intToByteArray(serverId), Util.intToByteArray(requestSize), }); for (int i = 0; i < replyFields; i++) { result = Util.concatArrays(result, Util.intToByteArray(distinctReplySizes[i])); result = Util.concatArrays(result, Util.intToByteArray(distinctReplyDelays[i])); } int payloadLength = Math.max(0, requestSize - (result.length + 4)); // additional 4 bytes for the header of "FragmentedMessage" if (payloadLength > 0) { byte[] payload = new byte[payloadLength]; synchronized (random) { random.nextBytes(payload); } result = Util.concatArrays(result, payload); } return FragmentedMessage.toSendableMessage(result); } }