/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.node; import java.util.Arrays; import java.util.ArrayList; import java.util.Comparator; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.SortedSet; import java.util.TreeSet; import freenet.crypt.Util; import freenet.support.Logger; import freenet.support.LogThresholdCallback; import freenet.support.Logger.LogLevel; class NPFPacket { private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } private int sequenceNumber; private final SortedSet<Integer> acks = new TreeSet<Integer>(); private final List<MessageFragment> fragments = new ArrayList<MessageFragment>(); /** Messages that are specific to a single packet and can be happily lost if it is lost. * They must be processed before the rest of the messages. * With early versions, these might be bogus, so be careful parsing them. */ private final List<byte[]> lossyMessages = new LinkedList<byte[]>(); private boolean error; private int length = 5; //Sequence number (4), numAcks(1) private int ackRangeCount = 0; private int ackBlockByteSize = 0; public static NPFPacket create(byte[] plaintext, BasePeerNode pn) { NPFPacket packet = new NPFPacket(); if (pn == null) throw new IllegalArgumentException("Can't estimate an ack type of received packet"); int offset = 0; if(plaintext.length < (offset + 5)) { //Sequence number + the number of acks packet.error = true; return packet; } packet.sequenceNumber = ((plaintext[offset] & 0xFF) << 24) | ((plaintext[offset + 1] & 0xFF) << 16) | ((plaintext[offset + 2] & 0xFF) << 8) | (plaintext[offset + 3] & 0xFF); offset += 4; //Process received acks int numAckRanges = plaintext[offset++] & 0xFF; if (numAckRanges > 0) { try { int ack, prevAck = 0; for(int i = 0; i < numAckRanges; i++) { if (i == 0) { ack = ((plaintext[offset] & 0xFF) << 24) | ((plaintext[offset + 1] & 0xFF) << 16) | ((plaintext[offset + 2] & 0xFF) << 8) | (plaintext[offset + 3] & 0xFF); offset += 4; } else { int distanceFromPrevious = (plaintext[offset++] & 0xFF); if (distanceFromPrevious != 0) { ack = prevAck + distanceFromPrevious; } else { // Far offset ack = ((plaintext[offset] & 0xFF) << 24) | ((plaintext[offset + 1] & 0xFF) << 16) | ((plaintext[offset + 2] & 0xFF) << 8) | (plaintext[offset + 3] & 0xFF); offset += 4; } } int rangeSize = (plaintext[offset++] & 0xFF); for (int j = 1; j <= rangeSize; j++) { packet.acks.add(ack++); } prevAck = ack-1; } } catch (ArrayIndexOutOfBoundsException e) { // The packet's length is not big enough packet.error = true; return packet; } } //Handle received message fragments int prevFragmentID = -1; while(offset < plaintext.length) { boolean shortMessage = (plaintext[offset] & 0x80) != 0; boolean isFragmented = (plaintext[offset] & 0x40) != 0; boolean firstFragment = (plaintext[offset] & 0x20) != 0; if(!isFragmented && !firstFragment) { // Padding or lossy messages. offset = tryParseLossyMessages(packet, plaintext, offset); break; } int messageID = -1; if((plaintext[offset] & 0x10) != 0) { if(plaintext.length < (offset + 4)) { packet.error = true; return packet; } messageID = ((plaintext[offset] & 0x0F) << 24) | ((plaintext[offset + 1] & 0xFF) << 16) | ((plaintext[offset + 2] & 0xFF) << 8) | (plaintext[offset + 3] & 0xFF); offset += 4; } else { if(plaintext.length < (offset + 2)) { packet.error = true; return packet; } if(prevFragmentID == -1) { Logger.warning(NPFPacket.class, "First fragment doesn't have full message id"); packet.error = true; return packet; } messageID = prevFragmentID + (((plaintext[offset] & 0x0F) << 8) | (plaintext[offset + 1] & 0xFF)); offset += 2; } prevFragmentID = messageID; int requiredLength = offset + (shortMessage ? 1 : 2) + (isFragmented ? (shortMessage ? 1 : 3) : 0); if(plaintext.length < requiredLength) { packet.error = true; return packet; } int fragmentLength; if(shortMessage) { fragmentLength = plaintext[offset++] & 0xFF; } else { fragmentLength = ((plaintext[offset] & 0xFF) << 8) | (plaintext[offset + 1] & 0xFF); offset += 2; } int messageLength = -1; int fragmentOffset = 0; if(isFragmented) { int value; if(shortMessage) { value = plaintext[offset++] & 0xFF; } else { value = ((plaintext[offset] & 0xFF) << 8) | (plaintext[offset + 1] & 0xFF); offset += 2; } if(firstFragment) { messageLength = value; if(messageLength == fragmentLength) { Logger.warning(NPFPacket.class, "Received fragmented message, but fragment contains the entire message"); } } else { fragmentOffset = value; } } else { messageLength = fragmentLength; } if((offset + fragmentLength) > plaintext.length) { Logger.error(NPFPacket.class, "Fragment doesn't fit in the received packet: offset is "+offset+" fragment length is "+fragmentLength+" plaintext length is "+plaintext.length+" message length "+messageLength+" message ID "+messageID+(pn == null ? "" : (" from "+pn.shortToString()))); packet.error = true; break; } byte[] fragmentData = Arrays.copyOfRange(plaintext, offset, offset + fragmentLength); offset += fragmentLength; packet.fragments.add(new MessageFragment(shortMessage, isFragmented, firstFragment, messageID, fragmentLength, messageLength, fragmentOffset, fragmentData, null)); } packet.length = offset; return packet; } private static int tryParseLossyMessages(NPFPacket packet, byte[] plaintext, int offset) { int origOffset = offset; while(true) { if(plaintext[offset] != 0x1F) return offset; // Padding // Else it might be some per-packet lossy messages offset++; if(offset >= plaintext.length) { packet.lossyMessages.clear(); return origOffset; } int len = plaintext[offset] & 0xFF; offset++; if(len > plaintext.length - offset) { packet.lossyMessages.clear(); return origOffset; } byte[] fragment = Arrays.copyOfRange(plaintext, offset, offset + len); packet.lossyMessages.add(fragment); offset += len; if(offset == plaintext.length) return offset; } } public int toBytes(byte[] buf, int offset, Random paddingGen) { int origOffset = offset; buf[offset] = (byte) (sequenceNumber >>> 24); buf[offset + 1] = (byte) (sequenceNumber >>> 16); buf[offset + 2] = (byte) (sequenceNumber >>> 8); buf[offset + 3] = (byte) (sequenceNumber); offset += 4; //Add acks buf[offset++] = (byte) (ackRangeCount); Iterator<Integer> acksIterator = acks.iterator(); if(acksIterator.hasNext()) { int startRange = 0, endRange = -1; int nextAck = acksIterator.next(); for (int i = 0; acksIterator.hasNext(); i++) { assert(nextAck - endRange >= 0); if (i == 0 || (nextAck - endRange >= 254)) { if(i != 0) buf[offset++] = (byte) 0; // Mark a far offset buf[offset] = (byte) (nextAck >>> 24); buf[offset + 1] = (byte) (nextAck >>> 16); buf[offset + 2] = (byte) (nextAck >>> 8); buf[offset + 3] = (byte) (nextAck); offset += 4; } else { assert(nextAck - endRange < 254); buf[offset++] = (byte) (nextAck - endRange); } endRange = startRange = nextAck; while(acksIterator.hasNext() && ((nextAck = acksIterator.next()) - endRange == 1) && (endRange - startRange < 254)) { endRange++; } byte rangeSize = (byte) (endRange - startRange + 1); buf[offset++] = rangeSize; // TODO: Add zero-cost dub-acks if any } if (nextAck != endRange) { // Edge-case when the last ack does not fit into previous range assert(nextAck - endRange >= 0); if (nextAck - endRange >= 254 && endRange != -1) { buf[offset++] = (byte) 0; // Mark a far offset } if (ackRangeCount == 1 || (nextAck - endRange >= 254)) { buf[offset] = (byte) (nextAck >>> 24); buf[offset + 1] = (byte) (nextAck >>> 16); buf[offset + 2] = (byte) (nextAck >>> 8); buf[offset + 3] = (byte) (nextAck); offset += 4; buf[offset++] = (byte) 1; } else { buf[offset++] = (byte) (nextAck - endRange); buf[offset++] = (byte) 1; } } } //Add fragments int prevFragmentID = -1; for(MessageFragment fragment : fragments) { if(fragment.shortMessage) buf[offset] = (byte) ((buf[offset] & 0xFF) | 0x80); if(fragment.isFragmented) buf[offset] = (byte) ((buf[offset] & 0xFF) | 0x40); if(fragment.firstFragment) buf[offset] = (byte) ((buf[offset] & 0xFF) | 0x20); if(prevFragmentID == -1 || (fragment.messageID - prevFragmentID >= 4096)) { buf[offset] = (byte) ((buf[offset] & 0xFF) | 0x10); buf[offset] = (byte) ((buf[offset] & 0xFF) | ((fragment.messageID >>> 24) & 0x0F)); buf[offset + 1] = (byte) (fragment.messageID >>> 16); buf[offset + 2] = (byte) (fragment.messageID >>> 8); buf[offset + 3] = (byte) (fragment.messageID); offset += 4; } else { int compressedMsgID = fragment.messageID - prevFragmentID; buf[offset] = (byte) ((buf[offset] & 0xFF) | ((compressedMsgID >>> 8) & 0x0F)); buf[offset + 1] = (byte) (compressedMsgID); offset += 2; } prevFragmentID = fragment.messageID; if(fragment.shortMessage) { buf[offset++] = (byte) (fragment.fragmentLength); } else { buf[offset] = (byte) (fragment.fragmentLength >>> 8); buf[offset + 1] = (byte) (fragment.fragmentLength); offset += 2; } if(fragment.isFragmented) { // If firstFragment is true, add total message length. Else, add fragment offset int value = fragment.firstFragment ? fragment.messageLength : fragment.fragmentOffset; if(fragment.shortMessage) { buf[offset++] = (byte) (value); } else { buf[offset] = (byte) (value >>> 8); buf[offset + 1] = (byte) (value); offset += 2; } } System.arraycopy(fragment.fragmentData, 0, buf, offset, fragment.fragmentLength); offset += fragment.fragmentLength; } if(!lossyMessages.isEmpty()) { for(byte[] msg : lossyMessages) { buf[offset++] = 0x1F; assert(msg.length <= 255); buf[offset++] = (byte) msg.length; System.arraycopy(msg, 0, buf, offset, msg.length); offset += msg.length; } } assert(offset - origOffset == length); if(offset < buf.length) { //More room, so add padding Util.randomBytes(paddingGen, buf, offset, buf.length - offset); byte b = (byte) (buf[offset] & 0x9F); //Make sure firstFragment and isFragmented isn't set if(b == 0x1F) b = (byte)0x9F; // Make sure it doesn't match the pattern for lossy messages buf[offset] = b; } return offset; } public boolean addAck(int ack, int maxPacketSize) { if(ack < 0) throw new IllegalArgumentException("Got negative ack: " + ack); if(acks.contains(ack)) return true; acks.add(ack); int nearRangeCount = 0, farRangeCount = 0; Iterator<Integer> acksIterator = acks.iterator(); int startRange = 0, endRange = -1; int nextAck = acksIterator.next(); while (acksIterator.hasNext()) { if (nextAck - endRange > 254 && endRange != -1) { farRangeCount++; } else { nearRangeCount++; } endRange = startRange = nextAck; while(acksIterator.hasNext() && ((nextAck = acksIterator.next()) - endRange == 1) && (endRange - startRange < 254)) { endRange++; } // TODO: Add zero-cost dub-acks if any } if (nextAck != endRange) { if (nextAck - endRange < 254 || endRange == -1) { nearRangeCount++; } else { farRangeCount++; } } if (nearRangeCount + farRangeCount > 254) { acks.remove(ack); return false; } // (start + offset) + (rangeCount-1) *(1byte deltaFromPrevios + length) + farRangeCount*(flag + 4byte packetSequenceNumber + length) int blockSize = 5 + (nearRangeCount-1)*2 + farRangeCount*6; int finalLength = length + blockSize - ackBlockByteSize; if(finalLength > maxPacketSize) { acks.remove(ack); return false; } length = finalLength; ackBlockByteSize = blockSize; ackRangeCount = farRangeCount + nearRangeCount; return true; } private int oldMsgIDLength; public int addMessageFragment(MessageFragment frag) { length += frag.length(); fragments.add(frag); Collections.sort(fragments, new MessageFragmentComparator()); int msgIDLength = 0; int prevMessageID = -1; for(MessageFragment fragment : fragments) { if((prevMessageID == -1) || (fragment.messageID - prevMessageID >= 4096)) { msgIDLength += 2; } prevMessageID = fragment.messageID; } length += (msgIDLength - oldMsgIDLength); oldMsgIDLength = msgIDLength; return length; } public int addLossyMessage(byte[] buf) { if(buf.length > 255) throw new IllegalArgumentException(); lossyMessages.add(buf); length += buf.length + 2; return length; } public boolean addLossyMessage(byte[] buf, int maxPacketSize) { if(length + buf.length + 2 > maxPacketSize) return false; if(buf.length > 255) throw new IllegalArgumentException(); lossyMessages.add(buf); length += buf.length + 2; return true; } public void removeLossyMessage(byte[] buf) { if(lossyMessages.remove(buf)) { length -= buf.length + 2; } } /** Get the list of lossy messages. Note that for early versions these may be bogus, * so be careful when parsing them. Note also that these must be processed before the * rest of the messages on the packet. */ public List<byte[]> getLossyMessages() { return lossyMessages; } public boolean getError() { return error; } public List<MessageFragment> getFragments() { return fragments; } public int getSequenceNumber() { return sequenceNumber; } public void setSequenceNumber(int sequenceNumber) { this.sequenceNumber = sequenceNumber; } public SortedSet<Integer> getAcks() { return acks; } public int getLength() { return length; } @Override public String toString() { return "Packet " + sequenceNumber + ": " + length + " bytes, " + acks.size() + " acks, " + fragments.size() + " fragments"; } private static class MessageFragmentComparator implements Comparator<MessageFragment> { @Override public int compare(MessageFragment frag1, MessageFragment frag2) { if(frag1.messageID < frag2.messageID) return -1; if(frag1.messageID == frag2.messageID) return 0; return 1; } } public void onSent(int totalPacketLength, BasePeerNode pn) { int totalMessageData = 0; int size = fragments.size(); int biggest = 0; for(MessageFragment frag: fragments) { totalMessageData += frag.fragmentLength; size++; if(biggest < frag.messageLength) biggest = frag.messageLength; } int overhead = totalPacketLength - totalMessageData; if(logDEBUG) Logger.debug(this, "Total packet overhead: "+overhead+" for "+size+" messages total message length "+totalMessageData+" total packet length "+totalPacketLength+" biggest message "+biggest); for(MessageFragment frag: fragments) { // frag.wrapper is always non-null on sending. frag.wrapper.onSent(frag.fragmentOffset, frag.fragmentOffset + frag.fragmentLength - 1, overhead / size, pn); } } String fragmentsAsString() { return Arrays.toString(fragments.toArray()); } public int countAcks() { return acks.size(); } /** * @return True if there are no MessageFragment's to send. */ public boolean noFragments() { return fragments.isEmpty(); } }