/* 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 freenet.io.comm.AsyncMessageCallback; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.SparseBitmap; import freenet.support.Logger.LogLevel; public class MessageWrapper { private final MessageItem item; private final boolean isShortMessage; private final int messageID; private boolean reportedSent; private final long created; private int resends; //Sorted lists of non-overlapping ranges. If you need to lock both, lock sent first private final SparseBitmap acks = new SparseBitmap(); private final SparseBitmap sent = new SparseBitmap(); private final SparseBitmap everSent = new SparseBitmap(); private static volatile boolean logMINOR; private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } public MessageWrapper(MessageItem item, int messageID) { this.item = item; isShortMessage = item.buf.length <= 255; this.messageID = messageID; created = System.currentTimeMillis(); } private boolean alreadyAcked = false; public boolean ack(int start, int end) { return ack(start, end, null); } /** * Mark the given range as received. * * @param start the first byte to be marked * @param end the last byte to be marked * @param pn just for logging * @return True if the whole packet has now been acked. False otherwise. */ public boolean ack(int start, int end, BasePeerNode pn) { synchronized(acks) { acks.add(start, end); if(acks.contains(0, item.buf.length - 1)) { if(!alreadyAcked) { if(item.cb != null) { for(AsyncMessageCallback cb : item.cb) { cb.acknowledged(); } } alreadyAcked = true; if(logMINOR) Logger.minor(this, "Total round trip time for message "+messageID+" : "+item+" : "+(System.currentTimeMillis() - created)+" in "+resends+" resends"+(pn == null ? "" : " for "+pn.shortToString())); } return true; } } return false; } /** * Marks the given range of bytes as lost and returns the number of bytes * that were lost. The input range is inclusive. * @param start The first byte that is lost * @param end The last byte that is lost * @return The number of bytes lost */ public int lost(int start, int end) { if(logDEBUG) Logger.debug(this, "Lost from "+start+" to "+end+" on "+this.messageID); int size = end - start + 1; synchronized(sent) { synchronized(acks) { resends++; sent.remove(start, end); for(int[] range : acks) { if(range[1] < start) continue; if(range[0] > end) continue; int toAddStart = Math.max(start, range[0]); int toAddEnd = Math.min(end, range[1]); if(toAddStart == toAddEnd || toAddStart > toAddEnd) continue; Logger.warning(this, "Lost range (" + start + "->" + end + ") is overlapped by acked range (" + range[0] + "->" + range[1] + "). Adding " + toAddStart + "->" + toAddEnd + " to sent"); sent.add(toAddStart, toAddEnd); size -= (toAddEnd - toAddStart + 1); } } } return size; } public int getMessageID() { return messageID; } public int getLength() { return item.buf.length; } public boolean isFragmented(int length) { if(length < item.buf.length) { //Can't send everything, so we have to fragment return true; } synchronized(sent) { synchronized(acks) { if(sent.isEmpty() && acks.isEmpty()) { //We haven't sent anything yet, so we can send it in one fragment return false; } } if(sent.contains(0, item.buf.length - 1)) { //It can be sent in one go, and we have already sent everything return false; } } return true; } public int getPriority() { return item.getPriority(); } public boolean isFirstFragment() { synchronized(sent) { synchronized(acks) { return sent.isEmpty() && acks.isEmpty(); } } } /** * Returns a {@code MessageFragment} with a length of {@code maxLength} or * less, or {@code null} if there is nothing to send. Ranges that have been * returned by this function, and not marked as lost, and data that has been * acked is never returned. * @param maxLength The maximum length of the returned fragment * @return a {@code MessageFragment} with a length of {@code maxLength} or less */ public MessageFragment getMessageFragment(int maxLength) { int start = 0; int end = item.buf.length - 1; int dataLength; byte[] fragmentData; synchronized(sent) { for(int[] range : sent) { if(range[0] == start) { start = range[1] + 1; } else if (range[0] - start > 0) { end = range[0] - 1; } } if(start >= item.buf.length) { return null; } dataLength = maxLength - 2 //Message id + flags - (isShortMessage ? 1 : 2); //Fragment length if(isFragmented(dataLength)) { dataLength -= (isShortMessage ? 1 : 3); //Message length / fragment offset } dataLength = Math.min(end - start + 1, dataLength); if(dataLength <= 0) return null; fragmentData = Arrays.copyOfRange(item.buf, start, start+dataLength); sent.add(start, start + dataLength - 1); if(logDEBUG) Logger.debug(this, "Using range "+start+" to "+(start+dataLength-1)+" gives "+sent+" on "+messageID); } boolean isFragmented = !((start == 0) && (dataLength == item.buf.length)); return new MessageFragment(isShortMessage, isFragmented, start == 0, messageID, dataLength, item.buf.length, start, fragmentData, this); } public void onDisconnect() { item.onDisconnect(); } MessageItem getItem() { return item; } /** * Returns {@code true} if all the data of this {@code MessageWrapper} has been sent, and {@code false} otherwise. * @return {@code true} if all the data of this {@code MessageWrapper} has been sent */ public boolean allSent() { synchronized(sent) { return sent.contains(0, item.buf.length-1); } } /** * Called when data from this {@code MessageWrapper} is sent to another node. * @param start The first byte that is sent * @param end The last byte that is sent * @param overhead The number of extra bytes used to send this message */ public void onSent(int start, int end, int overhead, BasePeerNode pn) { int report = 0; int resent = 0; boolean completed = false; synchronized(sent) { if(everSent.contains(start, end)) { report = 0; resent = end - start + 1 + overhead; } else { report = everSent.notOverlapping(start, end); resent = end - start + 1 - report; if(report > 0 && resent == 0) report += overhead; else if(resent > 0 && report == 0) resent += overhead; else { report += (overhead / 2); resent += (overhead - (overhead / 2)); } } everSent.add(start, end); if(everSent.contains(0, item.buf.length-1)) { // Maybe completed if(reportedSent) completed = false; else { completed = true; reportedSent = true; } } } if(report != 0) item.onSent(report); if(resent != 0 && pn != null) pn.resentBytes(resent); if(completed) item.onSentAll(); } SparseBitmap getSent() { return new SparseBitmap(sent); } SparseBitmap getAcks() { return new SparseBitmap(acks); } }