/******************************************************************************* * 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.IOException; import java.io.InputStream; import java.util.Arrays; import staticContent.framework.util.FragmentedMessage; import staticContent.framework.util.Util; public class SendableTransaction extends Transaction { //private static SecureRandom random = new SecureRandom(); private int clientId; private long planedSendTime = Util.NOT_SET; // in nanosec (used to detect // delays introduced by the // scheduler) // private final static int BASIC_HEADER_LENGTH = 4 + 2 + 8 + 5*4 + 4; // // transaction id + additionalReplyFields + send-timestamp + 5 standard // fields (clientId, serverId ...) + header of "FragmentedMessage" private final static int REPLY_HEADER_LENGTH = 4 + 4 + 4; // transaction id // + reply // length + // header of // "FragmentedMessage"; // (if reply // length < 12: // overhead; if // reply length // > 12: // padding) private int replyCounter = 0; // private int headerLength = Util.NOT_SET; // private int payloadLength = Util.NOT_SET; private FragmentedMessage fragmentedMessage = new FragmentedMessage(); // data // structure // for // to // recreate // transactions // (requests/replies) // from // fragments public SendableTransaction(int transactionId, int sendDelay, int clientId, int serverId, int requestSize, int[] replySizes, int[] replyDelays) { super(transactionId, sendDelay, // clientId, serverId, requestSize, replySizes, replyDelays); this.clientId = clientId; } public SendableTransaction(int transactionId, int sendDelay, int clientId, int serverId, int requestSize, int replySize, int replyDelay) { super(transactionId, sendDelay, // clientId, serverId, requestSize, new int[] { replySize }, new int[] { replyDelay }); this.clientId = clientId; } public SendableTransaction(int transactionId, int sendDelay, int clientId, int serverId, int requestSize) { super(transactionId, sendDelay, // clientId, serverId, requestSize); this.clientId = clientId; } public SendableTransaction(String serializedTransaction) { super(serializedTransaction); } public SendableTransaction(InputStream inputStream) throws IOException { super(); byte[] message = FragmentedMessage.forceReadMessage(inputStream); extractRequestHeaderRecords(message); } /** * server-side */ public SendableTransaction() { super(); } /** * server-side */ public SendableTransaction(byte[] message) { super(); extractRequestHeaderRecords(message); } /** * (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() { 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); }*/ private void extractRequestHeaderRecords(byte[] message) { this.transactionId = Util.byteArrayToInt(Arrays.copyOfRange(message, 0, 4)); short replyFields = Util.byteArrayToShort(Arrays.copyOfRange(message, 4, 6)); this.timestampSend = Util.byteArrayToLong(Arrays.copyOfRange(message, 6, 14)); this.clientId = Util .byteArrayToInt(Arrays.copyOfRange(message, 14, 18)); this.serverId = Util .byteArrayToInt(Arrays.copyOfRange(message, 18, 22)); this.requestSize = Util.byteArrayToInt(Arrays.copyOfRange(message, 22, 26)); if (replyFields > 0) { super.distinctReplySizes = new int[replyFields]; super.distinctReplyDelays = new int[replyFields]; for (int i = 0; i < replyFields; i++) { super.distinctReplySizes[i] = Util.byteArrayToInt(Arrays .copyOfRange(message, 26 + (i * 8), 30 + (i * 8))); super.distinctReplyDelays[i] = Util.byteArrayToInt(Arrays .copyOfRange(message, 30 + (i * 8), 34 + (i * 8))); } } } /** * (server/mix-side) create payload for reply (data to be sent via the anon * channel) * * @return */ public byte[] createSendableReply() { assert containsReplies(); assert hasMoreReplies(); replyCounter++; assert replyCounter <= distinctReplySizes.length; int replySize = distinctReplySizes[replyCounter - 1]; if (replySize <= (REPLY_HEADER_LENGTH)) { byte[] result = Util.concatArrays(new byte[][] { Util.intToByteArray(transactionId), Util.intToByteArray(replySize) }); return FragmentedMessage.toSendableMessage(result); } else { byte[] payload = new byte[replySize - REPLY_HEADER_LENGTH]; synchronized (random) { random.nextBytes(payload); } payload = Util.concatArrays(new byte[][] { Util.intToByteArray(transactionId), Util.intToByteArray(replySize), payload }); return FragmentedMessage.toSendableMessage(payload); } } public boolean hasMoreReplies() { // System.out.println("Remaining Replys: "+ remainingReplies() ); return remainingReplies() > 0; // Joh // return remainingReplies() != 0 } public int remainingReplies() { if (!containsReplies()) return 0; return distinctReplySizes.length - replyCounter; } /** * (client-side) * * @param message */ private void extractReplyHeaderRecords(byte[] message) { replyCounter++; assert replyCounter <= distinctReplySizes.length; int transactionId = Util.byteArrayToInt(Arrays.copyOfRange(message, 0, 4)); assert transactionId == this.transactionId : "" + transactionId + "!=" + this.transactionId; int replySize = Util.byteArrayToInt(Arrays.copyOfRange(message, 4, 8)); int expectedReplySize = distinctReplySizes[replyCounter - 1]; assert replySize == expectedReplySize : "TA: " + transactionId + " Reply " + distinctReplySizes[replyCounter - 1] + " replySize" + replySize + "!= expectedReplySize" + expectedReplySize; } /** * (server/mix-side) returns whether further chunks are needed or if the * message is already received completely see * "addRequestChunk(payloadChunk)" * * @return */ public boolean needMoreRequestChunks() { return fragmentedMessage.isFullyReceived(); } /** * (server/mix-side) add a message chunk see "needMoreRequestChunks()" * * @return returns null if the complete payloadChunk was required or a * fraction of the payloadChunk otherwise */ public byte[] addRequestChunk(byte[] payloadChunk) { return fragmentedMessage.addFragment(payloadChunk); } /** * (client-side) returns whether further chunks are needed or if the message * is already received completely see "addReplyChunk(byte[] payloadChunk)" * * @return */ public boolean needMoreReplyChunks() { // System.out.println("TA: " +taid+ " HasMoreReplies: " + // hasMoreReplies()); // System.out.println("TA: " +taid+ " FM isFullyReceived: " + // fragmentedMessage.isFullyReceived()); // Entweder fehlen noch komplette Replies oder Teile einer unkompletten // Reply und die TAI muss die gleiche dieses ST-Objektes sein return (hasMoreReplies() || !fragmentedMessage.isFullyReceived()); // joh // return !hasMoreReplies() && fragmentedMessage.isFullyReceived(); } private boolean belongsToSendableTransaction(byte[] overhead) { if (overhead.length >= 8) { int nextTransactionId = Util.byteArrayToInt(Arrays.copyOfRange( overhead, 4, 8)); if (nextTransactionId == this.transactionId) { return true; } } return false; } /** * (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) { byte[] returnOverhead = null; // System.out.println("TA: "+taid+" needMoreReplyChunks=" + assert needMoreReplyChunks(); byte[] overhead = fragmentedMessage.addFragment(payloadChunk); if (fragmentedMessage.isFullyReceived()) { extractReplyHeaderRecords(fragmentedMessage.getRawMessage()); if (hasMoreReplies()) { // create data structure for reading next // reply this.fragmentedMessage = new FragmentedMessage(); } if (overhead != null && belongsToSendableTransaction(overhead)) { // assert that the method will only return bytes that do NOT // belong to ANY reply of this transaction (not only the // current transaction) returnOverhead = addReplyChunk(overhead); } else if (overhead != null && !belongsToSendableTransaction(overhead)) { returnOverhead = overhead; } } return returnOverhead; } /** * (client-side) read all replies from inputStream that belong to this * transaction. blocks the calling thread until the all replies are received * (for none-blocking receival, the method addReplyChunk(byte[] * payloadChunk) can be used). */ public void readReplies(InputStream inputStream) throws IOException { while (hasMoreReplies()) readSingleReply(inputStream); } /** * (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 readSingleReply(InputStream inputStream) throws IOException { assert containsReplies(); assert hasMoreReplies(); byte[] message = FragmentedMessage.forceReadMessage(inputStream); extractReplyHeaderRecords(message); } public long getPlanedSendTime() { return planedSendTime; } public void setPlanedSendTime(long planedSendTime) { this.planedSendTime = planedSendTime; } public int getClientId() { return this.clientId; } }