package speedytools.common.network.multipart; import net.minecraftforge.fml.relauncher.Side; import speedytools.common.network.Packet250Types; import speedytools.common.utilities.ErrorLog; import java.io.*; import java.util.Arrays; import java.util.BitSet; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; /** * User: The Grey Ghost * Date: 28/03/14 * Multipart Packets are used to send large amounts of data, bigger than will fit into a single Packet250CustomPayload. * They are designed to be tolerant of network faults such as dropped packets, out-of-order packets, duplicate copies * of the same packet, loss of connection, etc * See MultipartPacketHandler or multipartprotocols.txt for more information * Usage: * (1) Create a class extending MultipartPacket. Similar to MultipartPacket it should have two constructors * (a) For generating a send packet: the equivalent MultipartPacket constructor should be called, and then * later on once the packet is fully populated with the raw data to be sent, call * setRawData to complete the packet setup * (b) For generating a receive packet: the constructor should accept a packet, call the equivalent MultipartPacket constructor, * complete setup of the class, AND THEN CALL processIncomingPacket after construction is finished, to process the segment rawdata * For senders: * (1) call getNextUnsentSegment to retrieve the next unsent segment. Send it over the network. null means there are no more segments. * (2) as incoming packets (acknowledgements) arrive, use MultipartPacket.readUniqueID to find the packet ID, and then send them to processIncomingPacket * (3) once all segments are sent (allSegmentsSent, or getNextUnsentSegment returns null), wait for an appropriate time, then * check for unacknowledged packets (allSegmentsAcknowledged is false). If necessary: * (4) use getNextUnacknowledgedSegment to iterate through segments which were sent but no acknowledgement yet received. * use resetToOldestUnacknowledgedSegment, getAcknowledgementsReceivedFlag, resetAcknowledgementsReceivedFlag to monitor * incoming acknowledgements, and to control which packets are resent * (5) once allSegmentsAcknowledged() is true, the packet is finished and can be discarded * (6) transmission of the packet can be aborted by calling getAbortPacket() and sending it. * if you receive an Acknowledgment packet for an ID which doesn't exist, call the static getAbortPacketForLostPacket(packet) to * convert it to an abort packet and send it back * For receivers: * (1) Incoming packets are processed by processIncomingPacket. The uniqueID of an incoming packet can be inspected before processing * using the static readUniqueID * (2) Periodically, check getSegmentsReceivedFlag, and if some have been received, call getAcknowledgementPacket() and send it, also * call resetSegmentsReceivedFlag * (3) When allSegmentsReceived is true, the packet is complete and can be processed * (4) transmission of the packet can be aborted by calling getAbortPacket() and sending it. * if you receive a packet for an ID which you have aborted, call the static getAbortPacketForLostPacket(packet) to * convert it to an abort packet and send it back. (No abort packets are generated in response to an incoming abort packet) * (5) You should maintain a list of completed and aborted packet IDs for a suitable length of time, to avoid creating a new Multipart * packet in response to delayed packets. */ public abstract class MultipartPacket { /** * returns the unique ID for this packet * @return the packet's unique ID */ public int getUniqueID() { return uniquePacketID; } // public byte getPacketTypeID() {return commonHeaderInfo.packet250CustomPayloadID; } /** * attempt to process the incoming segment * @param packet * @return true for success */ public boolean processIncomingSegment(Packet250MultipartSegment packet) { if (!packet.isPacketIsValid()) { ErrorLog.defaultLog().info("Invalid incoming packet in processIncomingPacket"); return false; } if (packet.getUniqueMultipartID() != uniquePacketID) { ErrorLog.defaultLog().info("incoming unique ID " + packet.getUniqueMultipartID()+ " doesn't match multipart unique ID " + uniquePacketID); return false; } if (packet.getPacket250Type() != packet250Type) { ErrorLog.defaultLog().info("incoming packet250Type " + packet.getPacket250Type() + " doesn't match multipart packet250Type " + packet250Type); return false; } if (packet.isAbortTransmission()) { processAbort(); } else { try { processSegmentData(packet); } catch (IOException ioe) { ErrorLog.defaultLog().info(ioe.getMessage()); return false; } } return true; } /** * attempt to process the incoming segment * @param packet * @return true for success */ public boolean processIncomingAcknowledgement(Packet250MultipartSegmentAcknowledge packet) { if (!packet.isPacketIsValid()) { ErrorLog.defaultLog().info("Invalid incoming packet in processIncomingAcknowledgement"); return false; } if (packet.getUniqueID() != uniquePacketID) { ErrorLog.defaultLog().info("incoming unique ID " + packet.getUniqueID()+ " doesn't match multipart unique ID " + uniquePacketID); return false; } if (packet.getPacket250Type() != packet250Type.getPairedType()) { ErrorLog.defaultLog().info("incoming packet250Type " + packet.getPacket250Type() + " doesn't match multipart Ack packet250Type " + packet250Type.getPairedType()); return false; } switch (packet.getAcknowledgement()) { case ABORT: { processAbort(); break; } case ACKNOWLEDGE_ALL: { processAcknowledgeAll(packet); break; } case ACKNOWLEDGEMENT: { processAcknowledgement(packet); break; } default: { ErrorLog.defaultLog().info("Invalid acknowledgement " + packet.getAcknowledgement() + " in incoming Packet250MultipartSegmentAcknowledge"); return false; } } return true; } /** returns the next segment that has never been sent * @return the packet for the segment; null for failure or "no more unsent" */ public Packet250MultipartSegment getNextUnsentSegment() { assert(checkInvariants()); if (!iAmASender) return null; if (aborted) return null; assert (nextUnsentSegment >= 0); if (nextUnsentSegment >= segmentCount) return null; Packet250MultipartSegment retval = getSegmentByNumber(nextUnsentSegment); ++nextUnsentSegment; return retval; } /** returns the next in the sequence of unacknowledged segments * Note - will not return the same segment, even if unacknowledged, until resetToOldestUnacknowledgedSegment is called. * @return the packet for the segment; null for failure or for "no more sent and unacknowledged" */ public Packet250MultipartSegment getNextUnacknowledgedSegment() { assert(checkInvariants()); if (!iAmASender) return null; if (aborted) return null; if (nextUnacknowledgedSegment >= segmentCount || nextUnacknowledgedSegment >= nextUnsentSegment) return null; if (!segmentsNotAcknowledged.get(nextUnacknowledgedSegment)) { // i.e. this segment has been acknowledged nextUnacknowledgedSegment = segmentsNotAcknowledged.nextSetBit(nextUnacknowledgedSegment); if (nextUnacknowledgedSegment >= segmentCount || nextUnacknowledgedSegment >= nextUnsentSegment) return null; } Packet250MultipartSegment retval = getSegmentByNumber(nextUnacknowledgedSegment); ++nextUnacknowledgedSegment; return retval; } /** resets getNextUnacknowledgedSegment to the oldest unacknowledged segment */ public void resetToOldestUnacknowledgedSegment() { nextUnacknowledgedSegment = 0; } /** * have all segments been sent at least once? * @return true if I am a sender and all segments been sent at least once */ public boolean allSegmentsSent() { return iAmASender ? nextUnsentSegment >= segmentCount : false; } /** * have all segments been acknowledged? * @return true if I am a sender and all segments have been acknowledged */ public boolean allSegmentsAcknowledged() { return iAmASender ? segmentsNotAcknowledged.isEmpty() : false; } /** * have all segments been received? * @return true if I am a receiver and all segments have been received, */ public boolean allSegmentsReceived() { return iAmASender ? false : segmentsNotReceivedYet.isEmpty(); } /** * has this packet has been aborted? * @return true if the packet has been aborted */ public boolean hasBeenAborted() {return aborted; } /** * returns true if at least one new acknowledgement has been received since the last reset * (in order to be counted, the acknowledgement must have included at least one * segment not previously acknowledged) * @return */ public boolean getAcknowledgementsReceivedFlag() { return acknowledgementsReceivedFlag; } /** * reset the acknowledgementsReceived flag */ public void resetAcknowledgementsReceivedFlag() { acknowledgementsReceivedFlag = false; } /** * returns true if at least one new segment has been received since the last reset * (segments which have been received previously don't count) * @return */ public boolean getSegmentsReceivedFlag() { return segmentsReceivedFlag; } /** * reset the segmentsReceivedFlag flag */ public void resetSegmentsReceivedFlag() { segmentsReceivedFlag = false; } /** returns the percentage completion of the transmission * * @return 0 - 100 inclusive */ public int getPercentComplete() { if (iAmASender) { int segmentsComplete = nextUnsentSegment + (segmentCount - segmentsNotAcknowledged.cardinality()); return 100 * segmentsComplete / 2 / segmentCount; } else { return 100 - (100 * segmentsNotReceivedYet.cardinality() / segmentCount); } } /** * create a packet to inform of all the segments received to date * @return the packet, or null for a problem */ public Packet250MultipartSegmentAcknowledge getAcknowledgementPacket() { assert(checkInvariants()); if (iAmASender) return null; if (aborted) return null; Packet250MultipartSegmentAcknowledge retval = null; byte [] rawBitsetBytes = segmentsNotReceivedYet.toByteArray(); if (rawBitsetBytes.length > MAX_SEGMENT_SIZE) { ErrorLog.defaultLog().info("Failed to getAcknowledgementPacket, because acknowledgement bitset length " + rawBitsetBytes.length + " is > MAXSEGMENTSIZE" + MAX_SEGMENT_SIZE); return null; } retval = new Packet250MultipartSegmentAcknowledge(packet250Type.getPairedType(), Packet250MultipartSegmentAcknowledge.Acknowledgement.ACKNOWLEDGEMENT, uniquePacketID, segmentsNotReceivedYet); return retval; } /** * aborts this multipartPacket, and creates a packet to inform that this multipartPacket has been aborted * @return the packet, or null for a problem */ public Packet250MultipartSegment getSenderAbortPacket() { assert(checkInvariants()); Packet250MultipartSegment retval = new Packet250MultipartSegment(packet250Type, true, uniquePacketID, (short)0, (short)segmentSize, rawData.length, null); aborted = true; return retval; } /** * aborts this multipartPacket, and creates a packet to inform that this multipartPacket has been aborted * @return the packet, or null for a problem */ public Packet250MultipartSegmentAcknowledge getReceiverAbortPacket() { assert(checkInvariants()); Packet250MultipartSegmentAcknowledge retval = new Packet250MultipartSegmentAcknowledge(packet250Type.getPairedType(), Packet250MultipartSegmentAcknowledge.Acknowledgement.ABORT, uniquePacketID, null); aborted = true; return retval; } /** * create a packet to inform that this multipartPacket lostPacket has been aborted * @param acknowledgeAbortPackets - if true, generate an abort packet even if the lostPacket is itself an abort packet * @return the abort packet, or null if not possible, or if the lostPacket is itself an abort packet and acknowledgeAbortPackets is false */ public static Packet250MultipartSegmentAcknowledge getAbortPacketForLostPacket(Packet250MultipartSegment lostPacket, boolean acknowledgeAbortPackets) { if (lostPacket.isAbortTransmission() && !acknowledgeAbortPackets) return null; Packet250MultipartSegmentAcknowledge retval = new Packet250MultipartSegmentAcknowledge(lostPacket.getPacket250Type().getPairedType(), Packet250MultipartSegmentAcknowledge.Acknowledgement.ABORT, lostPacket.getUniqueMultipartID(), null); return retval; } /** * create a packet to inform that this multipartPacket lostPacket has been aborted * @return the abort packet, or null if not possible, or if the lostPacket is itself an abort packet */ public static Packet250MultipartSegment getAbortPacketForLostPacket(Packet250MultipartSegmentAcknowledge lostPacket) { if (lostPacket.getAcknowledgement() == Packet250MultipartSegmentAcknowledge.Acknowledgement.ABORT) return null; Packet250MultipartSegment retval = new Packet250MultipartSegment(lostPacket.getPacket250Type().getPairedType(), true, lostPacket.getUniqueID(), (short)0, (short)0, 0, null); return retval; } /** * create a packet to inform that this multipartPacket lostPacket has been completed (ACKNOWLEDGE ALL) * @return the acknowledge packet, or null if not possible, or an abort packet if the lostPacket is an abort packet */ public static Packet250MultipartSegmentAcknowledge getFullAcknowledgePacketForLostPacket(Packet250MultipartSegment lostPacket) { if (lostPacket.isAbortTransmission()) return getAbortPacketForLostPacket(lostPacket, true); Packet250MultipartSegmentAcknowledge retval = new Packet250MultipartSegmentAcknowledge(lostPacket.getPacket250Type().getPairedType(), Packet250MultipartSegmentAcknowledge.Acknowledgement.ACKNOWLEDGE_ALL, lostPacket.getUniqueMultipartID(), null); return retval; } /** * get the packet for the given segment number * @param segmentNumber from 0 up to segmentCount - 1 inclusive * @return the packet, or null for failure */ protected Packet250MultipartSegment getSegmentByNumber(int segmentNumber) { Packet250MultipartSegment retval = null; try { if (segmentNumber < 0 || segmentNumber >= segmentCount) throw new IOException("Invalid segment number:" + segmentNumber); int startBuffPos = segmentNumber * segmentSize; int segmentLength = Math.min(segmentSize, rawData.length - startBuffPos); assert (segmentLength <= Short.MAX_VALUE); assert (segmentNumber <= Short.MAX_VALUE); assert (segmentSize <= Short.MAX_VALUE); byte [] segmentToSend = Arrays.copyOfRange(rawData, startBuffPos, startBuffPos + segmentLength); retval = new Packet250MultipartSegment(packet250Type, false, uniquePacketID, (short)segmentNumber, (short)segmentSize, rawData.length, segmentToSend); } catch (IOException ioe) { ErrorLog.defaultLog().info("Failed to getAcknowledgementPacket, due to exception " + ioe.toString()); return null; } catch (IllegalArgumentException iae) { ErrorLog.defaultLog().info("Failed to getAcknowledgementPacket, due to exception " + iae.toString()); return null; } return retval; } /** Generate a MultipartPacket to be used for sending data * It is initialised to have no data; the sub class must add the rawData later using setRawData * @param i_whichSideAmIOn is this packet being created on the server side or the client side? * @param i_packet250Type The custom payload ID to use when sending packets * @param i_segmentSize The segment size to be used */ protected MultipartPacket(Packet250Types i_packet250Type, Side i_whichSideAmIOn, int i_segmentSize) { iAmASender = true; whichSideAmIOn = i_whichSideAmIOn; packet250Type = i_packet250Type; if (i_segmentSize <= 0 || i_segmentSize > MAX_SEGMENT_SIZE) throw new IllegalArgumentException("segmentSize " + i_segmentSize + " is out of range [0 - " + MAX_SEGMENT_SIZE + "]"); segmentSize = i_segmentSize; uniquePacketID = (nextUniquePacketID << 1) + (whichSideAmIOn == Side.SERVER ? 1 : 0); nextUniquePacketID++; segmentCount = 0; rawData = null; segmentsNotAcknowledged = null; segmentsNotReceivedYet = null; acknowledgementsReceivedFlag = false; segmentsReceivedFlag = false; nextUnsentSegment = 0; nextUnacknowledgedSegment = 0; aborted = false; } /** sets up the MultipartPacket from the incoming packet containing segment data * *** DOES NOT PROCESS THE PACKET, the subclass should call processIncomingPacket after construction is finished * @param packet * @throws IOException if an error occurs or the packet doesn't contain segment data */ protected MultipartPacket(Packet250MultipartSegment packet) throws IOException { if (!packet.isPacketIsValid()) throw new IOException("Invalid Packet250MultipartSegment"); if (packet.isAbortTransmission()) throw new IOException("Tried to create a new Multipart packet from an abort packet"); packet250Type = packet.getPacket250Type(); uniquePacketID = packet.getUniqueMultipartID(); segmentSize = packet.getSegmentSize(); int fullMultipartLength = packet.getFullMultipartLength(); if (segmentSize <= 0 || segmentSize > MAX_SEGMENT_SIZE) throw new IOException("Packet segment size " + segmentSize + " out of allowable range [1 - " + MAX_SEGMENT_SIZE + "]"); int checkSegmentCount = (fullMultipartLength + segmentSize - 1) / segmentSize; if (checkSegmentCount <= 0 || checkSegmentCount > MAX_SEGMENT_COUNT) throw new IOException("Segment count " + checkSegmentCount + " out of allowable range [1 - " + MAX_SEGMENT_COUNT + "]"); prepareSpaceForRawData(new byte[fullMultipartLength]); } private MultipartPacket() {}; /** set the raw data for this packet and initialise the associated segment data * @param newRawData the rawdata to be copied into the packet */ protected void setRawDataForSending(byte[] newRawData) { byte [] compressedRawData = compress(newRawData); setRawData(compressedRawData); } protected void prepareSpaceForRawData(byte [] newRawData) { setRawData(newRawData); } private void setRawData(byte [] newRawData) { int totalLength = newRawData.length; if (totalLength > MAX_SEGMENT_COUNT * segmentSize) { throw new IllegalArgumentException("RawData size " + totalLength + " is greater than the maximum " + segmentSize * MAX_SEGMENT_COUNT + "for segment size " + segmentSize); } segmentCount = (totalLength + segmentSize - 1) / segmentSize; rawData = newRawData; if (iAmASender) { segmentsNotAcknowledged = new BitSet(segmentCount); segmentsNotAcknowledged.set(0, segmentCount); } else { segmentsNotReceivedYet = new BitSet(segmentCount); segmentsNotReceivedYet.set(0, segmentCount); } assert checkInvariants(); } static private final int START_FLAG_SIZE = 1; static private final int START_LEN_SIZE = 4; /** try to compress the data: * the returned value is * retval[0] = 0 -> uncompressed, retval[1..] is the uncompressed data * retval[0] !=0 -> compressed, retval[1..] is the compressed data * @param dataToCompress * @return */ protected byte [] compress(byte [] dataToCompress) { int uncompressedLength = dataToCompress.length; Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION); try { deflater.setInput(dataToCompress); deflater.finish(); byte[] compressedData = new byte[START_FLAG_SIZE + START_LEN_SIZE + dataToCompress.length]; int compressedSize = deflater.deflate(compressedData,START_FLAG_SIZE + START_LEN_SIZE, compressedData.length - START_FLAG_SIZE - START_LEN_SIZE ); if (compressedSize + START_FLAG_SIZE + START_LEN_SIZE < uncompressedLength + START_FLAG_SIZE) { // worthwhile final int COMPRESSED_FLAG = 1; compressedData[0] = COMPRESSED_FLAG; int i = START_FLAG_SIZE; compressedData[i++] = (byte)(uncompressedLength & 0xff); compressedData[i++] = (byte)((uncompressedLength >> 8) & 0xff); compressedData[i++] = (byte)((uncompressedLength >> 16) & 0xff); compressedData[i++] = (byte)((uncompressedLength >> 24) & 0xff); compressedSize += START_FLAG_SIZE + START_LEN_SIZE; // System.out.println("Compressed multipartPart from " + dataToCompress.length + " to " + compressedSize); return Arrays.copyOf(compressedData, compressedSize); } } catch (Exception exception) { ErrorLog.defaultLog().info("Deflate error:" + exception); } finally { deflater.end(); } // just do uncompressed byte [] retval = new byte[START_FLAG_SIZE + dataToCompress.length]; final int UNCOMPRESSED_FLAG = 0; retval[0] = UNCOMPRESSED_FLAG; System.arraycopy(dataToCompress, 0, retval, START_FLAG_SIZE, dataToCompress.length); // System.out.println("Uncompressed multipartPart " + dataToCompress.length); return retval; } /** * decompress the data. * @param dataToUncompress first byte is a flag: 0 = remaining data is uncompressed; !=0 = remaining data is compressed * If compressed, the next 4 bytes are the uncompressed length as an int * @return null for a problem */ protected byte [] uncompress(byte [] dataToUncompress) { if (dataToUncompress.length < START_FLAG_SIZE) { return null; } if (dataToUncompress.length == START_FLAG_SIZE) { return new byte[0]; } if (dataToUncompress[0] == 0) { // no compression, just remove flag return Arrays.copyOfRange(rawData, START_FLAG_SIZE, rawData.length); } if (dataToUncompress.length < START_FLAG_SIZE + START_LEN_SIZE) { return null; } int uncompressedLength = 0; final int BYTES_PER_INT = 4; for (int i = START_FLAG_SIZE + BYTES_PER_INT - 1; i >= START_FLAG_SIZE ; --i) { uncompressedLength = (uncompressedLength << 8) | ((int) dataToUncompress[i] & 0xff); } if (uncompressedLength < 0 || uncompressedLength > MAX_REASONABLE_TOTAL_SIZE) { return null; } byte [] retval = new byte[uncompressedLength]; Inflater inflater = new Inflater(); inflater.setInput(dataToUncompress, START_FLAG_SIZE + START_LEN_SIZE, dataToUncompress.length - START_FLAG_SIZE - START_LEN_SIZE); try { inflater.inflate(retval); } catch (DataFormatException dataformatexception) { ErrorLog.defaultLog().info("MultipartPacket decompression failed:" + dataformatexception); return null; } finally { inflater.end(); } return retval; } /** retrieve a copy of the packet's raw data * @return the raw data, or null if the packet isn't finished yet (error) */ protected byte [] getRawDataCopy() { if (!iAmASender && !allSegmentsReceived()) return null; return uncompress(rawData); } /** abort this transmission */ protected void processAbort() { aborted = true; assert checkInvariants(); } /** fully acknowledge this packet * @throws IOException */ protected void processAcknowledgeAll(Packet250MultipartSegmentAcknowledge packet) { if (!iAmASender) { ErrorLog.defaultLog().info("received acknowledgement packet on receiver side"); } if (segmentsNotAcknowledged.isEmpty()) return; segmentsNotAcknowledged.clear(); nextUnsentSegment = segmentCount; acknowledgementsReceivedFlag = true; nextUnacknowledgedSegment = segmentCount; assert checkInvariants(); } /** incorporate the data for this segment into the packet * @throws IOException */ protected void processSegmentData(Packet250MultipartSegment segmentPacket) throws IOException { short segmentNumber = segmentPacket.getSegmentNumber(); short pktSegmentSize = segmentPacket.getSegmentSize(); int rawdataLength = segmentPacket.getFullMultipartLength(); if (segmentNumber < 0 || segmentNumber >= segmentCount) throw new IOException("Packet segment number " + segmentNumber + " outside valid range [0 - " + (segmentCount-1) + "] inclusive"); if (!segmentsNotReceivedYet.get(segmentNumber)) return; // duplicate of a segment already received, just ignore it if (pktSegmentSize != segmentSize) throw new IOException("Packet segment size " + pktSegmentSize + " does not match expected (" + segmentSize + ")"); if (rawdataLength != rawData.length) throw new IOException("Packet rawdataLength " + rawdataLength + " does not match expected (" + rawData.length + ")"); int startBuffPos = segmentNumber * segmentSize; int expectedSegmentLength = Math.min(segmentSize, rawData.length - startBuffPos); assert (expectedSegmentLength < Short.MAX_VALUE); try { System.arraycopy(segmentPacket.getRawData(), 0, rawData, startBuffPos, expectedSegmentLength); } catch (IndexOutOfBoundsException iobe) { ErrorLog.defaultLog().info("Size of segment data read " + expectedSegmentLength + " was too big for the target buffer "); } segmentsNotReceivedYet.clear(segmentNumber); segmentsReceivedFlag = true; assert checkInvariants(); } /** integrate the acknowledgement bits into this packet * @throws IOException */ protected void processAcknowledgement(Packet250MultipartSegmentAcknowledge acknowledgementPacket) { if (!iAmASender) { ErrorLog.defaultLog().info("received Packet250MultipartSegmentAcknowledge on receiver side"); return; } BitSet notAcknowledgedNew = acknowledgementPacket.getSegmentsNotReceivedYet(); // check to see if we have received acknowledgement for any segments we haven't sent yet! // i.e. if the last zero is greater than the sent segment count int lastAcknowledgedPacket = notAcknowledgedNew.previousClearBit(segmentCount - 1); if (lastAcknowledgedPacket >= nextUnsentSegment) { ErrorLog.defaultLog().info("acknowledged segment was never sent in incoming Packet250MultipartSegmentAcknowledge"); return; } BitSet savedNack = (BitSet)segmentsNotAcknowledged.clone(); segmentsNotAcknowledged.and(notAcknowledgedNew); if (!savedNack.equals(segmentsNotAcknowledged)) acknowledgementsReceivedFlag = true; assert checkInvariants(); } // derived classes should implement this interface so that others wishing to create a new MultipartPacket (in response to an incoming packet) can pass this object to the packet handler which will invoke it. public interface MultipartPacketCreator { public MultipartPacket createNewPacket(Packet250MultipartSegment packet); } /** * checks this class to see if it is internally consistent * @return true if ok, false if bad. */ protected boolean checkInvariants() { if (aborted) return true; if (segmentSize <= 0 || segmentSize > MAX_SEGMENT_SIZE) return false; if (segmentCount <= 0 || segmentCount > MAX_SEGMENT_COUNT) return false; if (iAmASender) { if (nextUnsentSegment < 0 || nextUnsentSegment > segmentCount) return false; if (nextUnacknowledgedSegment < 0 || nextUnacknowledgedSegment > segmentCount) return false; if (segmentsNotAcknowledged.length() > segmentCount) return false; if (segmentsNotAcknowledged == null || segmentsNotAcknowledged.length() > segmentCount) return false; if (segmentsNotAcknowledged.previousClearBit(segmentCount-1) > nextUnsentSegment) return false; if ((uniquePacketID & 1) != (whichSideAmIOn == Side.SERVER ? 1 : 0 )) return false; } else { if (segmentsNotReceivedYet == null || segmentsNotReceivedYet.length() > segmentCount) return false; } return true; } public static final int MAX_SEGMENT_COUNT = Short.MAX_VALUE; public static final int MAX_SEGMENT_SIZE = 30000; public static final int MAX_REASONABLE_TOTAL_SIZE = 10 * 1000 * 1000; public static final int NULL_PACKET_ID = -1; private Side whichSideAmIOn; private Packet250Types packet250Type; private int uniquePacketID; private int segmentSize; public int getSegmentCount() { return segmentCount; } private int segmentCount; private byte [] rawData; // the raw data. The first byte is compression flag; non-zero --> compressed private boolean iAmASender; // true if this packet is being sent; false if it's being received. private BitSet segmentsNotAcknowledged; private boolean acknowledgementsReceivedFlag; // set to true once one or more acknowledgements have been received private int nextUnsentSegment = -1; // the next segment we have never sent. -1 is dummy value to trigger error if we forget to initialise private int nextUnacknowledgedSegment = -1; // the next unacknowledged segment. private BitSet segmentsNotReceivedYet; private boolean segmentsReceivedFlag; // set to true once one or more segments have been received private boolean aborted = false; private static int nextUniquePacketID = 0; // unique across all packets sent from this server and derived from MultipartPacket }