package net.i2p.router.transport.udp;
import java.util.Map;
import net.i2p.data.DataFormatException;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import net.i2p.router.util.DecayingBloomFilter;
import net.i2p.router.util.DecayingHashSet;
import net.i2p.util.Log;
/**
* Organize the received data message fragments, feeding completed messages
* to the {@link MessageReceiver} and telling the {@link ACKSender} of new
* peers to ACK. In addition, it drops failed fragments and keeps a
* minimal list of the most recently completed messages (even though higher
* up in the router we have full blown replay detection, its nice to have a
* basic line of defense here).
*
*/
class InboundMessageFragments /*implements UDPTransport.PartialACKSource */{
private final RouterContext _context;
private final Log _log;
/** list of message IDs recently received, so we can ignore in flight dups */
private DecayingBloomFilter _recentlyCompletedMessages;
private final OutboundMessageFragments _outbound;
private final UDPTransport _transport;
private final ACKSender _ackSender;
private final MessageReceiver _messageReceiver;
private volatile boolean _alive;
/** decay the recently completed every 20 seconds */
private static final int DECAY_PERIOD = 10*1000;
public InboundMessageFragments(RouterContext ctx, OutboundMessageFragments outbound, UDPTransport transport) {
_context = ctx;
_log = ctx.logManager().getLog(InboundMessageFragments.class);
//_inboundMessages = new HashMap(64);
_outbound = outbound;
_transport = transport;
_ackSender = new ACKSender(_context, _transport);
_messageReceiver = new MessageReceiver(_context, _transport);
_context.statManager().createRateStat("udp.receivedCompleteTime", "How long it takes to receive a full message", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.receivedCompleteFragments", "How many fragments go in a fully received message", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.receivedACKs", "How many messages were ACKed at a time", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.ignoreRecentDuplicate", "Take note that we received a packet for a recently completed message", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.receiveMessagePeriod", "How long it takes to pull the message fragments out of a packet", "udp", UDPTransport.RATES);
//_context.statManager().createRateStat("udp.receiveACKPeriod", "How long it takes to pull the ACKs out of a packet", "udp", UDPTransport.RATES);
_context.statManager().createRateStat("udp.receivePiggyback", "How many acks were included in a packet with data fragments (time == # data fragments)", "udp", UDPTransport.RATES);
}
public synchronized void startup() {
_alive = true;
// may want to extend the DecayingBloomFilter so we can use a smaller
// array size (currently its tuned for 10 minute rates for the
// messageValidator)
_recentlyCompletedMessages = new DecayingHashSet(_context, DECAY_PERIOD, 4, "UDPIMF");
_ackSender.startup();
_messageReceiver.startup();
}
public synchronized void shutdown() {
_alive = false;
if (_recentlyCompletedMessages != null)
_recentlyCompletedMessages.stopDecaying();
_recentlyCompletedMessages = null;
_ackSender.shutdown();
_messageReceiver.shutdown();
}
public boolean isAlive() { return _alive; }
/**
* Pull the fragments and ACKs out of the authenticated data packet
*/
public void receiveData(PeerState from, UDPPacketReader.DataReader data) {
try {
rcvData(from, data);
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad pkt from: " + from, dfe);
} catch (IndexOutOfBoundsException ioobe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad pkt from: " + from, ioobe);
}
}
/**
* Pull the fragments and ACKs out of the authenticated data packet
*/
private void rcvData(PeerState from, UDPPacketReader.DataReader data) throws DataFormatException {
//long beforeMsgs = _context.clock().now();
int fragmentsIncluded = receiveMessages(from, data);
//long afterMsgs = _context.clock().now();
int acksIncluded = receiveACKs(from, data);
//long afterACKs = _context.clock().now();
from.packetReceived(data.getPacketSize());
// each of these was less than 0.1 ms
//_context.statManager().addRateData("udp.receiveMessagePeriod", afterMsgs-beforeMsgs, afterACKs-beforeMsgs);
//_context.statManager().addRateData("udp.receiveACKPeriod", afterACKs-afterMsgs, afterACKs-beforeMsgs);
if ( (fragmentsIncluded > 0) && (acksIncluded > 0) )
_context.statManager().addRateData("udp.receivePiggyback", acksIncluded, fragmentsIncluded);
}
/**
* Pull out all the data fragments and shove them into InboundMessageStates.
* Along the way, if any state expires, or a full message arrives, move it
* appropriately.
*
* @return number of data fragments included
*/
private int receiveMessages(PeerState from, UDPPacketReader.DataReader data) throws DataFormatException {
int fragments = data.readFragmentCount();
if (fragments <= 0) return fragments;
Hash fromPeer = from.getRemotePeer();
Map<Long, InboundMessageState> messages = from.getInboundMessages();
for (int i = 0; i < fragments; i++) {
long mid = data.readMessageId(i);
Long messageId = Long.valueOf(mid);
if (_recentlyCompletedMessages.isKnown(mid)) {
// Only update stats for the first fragment,
// otherwise it wildly overstates things
if (data.readMessageFragmentNum(i) == 0) {
_context.statManager().addRateData("udp.ignoreRecentDuplicate", 1);
from.messageFullyReceived(messageId, -1);
_ackSender.ackPeer(from);
if (_log.shouldLog(Log.INFO))
_log.info("Message received is a dup: " + mid + " dups: "
+ _recentlyCompletedMessages.getCurrentDuplicateCount() + " out of "
+ _recentlyCompletedMessages.getInsertedCount());
_context.messageHistory().droppedInboundMessage(mid, from.getRemotePeer(), "dup");
}
continue;
}
InboundMessageState state;
boolean messageComplete = false;
boolean messageExpired = false;
boolean fragmentOK;
boolean partialACK = false;
synchronized (messages) {
boolean isNew = false;
state = messages.get(messageId);
if (state == null) {
state = new InboundMessageState(_context, mid, fromPeer, data, i);
isNew = true;
fragmentOK = true;
// we will add to messages shortly if it isn't complete
} else {
fragmentOK = state.receiveFragment(data, i);
}
if (state.isComplete()) {
messageComplete = true;
if (!isNew)
messages.remove(messageId);
} else if (state.isExpired()) {
messageExpired = true;
if (!isNew)
messages.remove(messageId);
} else {
partialACK = true;
if (isNew)
messages.put(messageId, state);
}
}
if (messageComplete) {
_recentlyCompletedMessages.add(mid);
from.messageFullyReceived(messageId, state.getCompleteSize());
_ackSender.ackPeer(from);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message received completely! " + state);
_context.statManager().addRateData("udp.receivedCompleteTime", state.getLifetime(), state.getLifetime());
if (state.getFragmentCount() > 0)
_context.statManager().addRateData("udp.receivedCompleteFragments", state.getFragmentCount(), state.getLifetime());
// this calls state.releaseResources(), all state access must be before this
_messageReceiver.receiveMessage(state);
} else if (messageExpired) {
if (_log.shouldLog(Log.WARN))
_log.warn("Message expired while only being partially read: " + state);
_context.messageHistory().droppedInboundMessage(state.getMessageId(), state.getFrom(), "expired while partially read: " + state.toString());
// all state access must be before this
state.releaseResources();
} else if (partialACK) {
// not expired but not yet complete... lets queue up a partial ACK
if (_log.shouldLog(Log.DEBUG))
_log.debug("Queueing up a partial ACK for peer: " + from + " for " + state);
from.messagePartiallyReceived();
_ackSender.ackPeer(from);
}
// TODO: Why give up on other fragments if one is bad?
if (!fragmentOK)
break;
}
from.expireInboundMessages();
return fragments;
}
/**
* @return the number of bitfields in the ack? why?
*/
private int receiveACKs(PeerState from, UDPPacketReader.DataReader data) throws DataFormatException {
int rv = 0;
boolean newAck = false;
if (data.readACKsIncluded()) {
int ackCount = data.readACKCount();
if (ackCount > 0) {
rv += ackCount;
_context.statManager().addRateData("udp.receivedACKs", ackCount);
//_context.statManager().getStatLog().addData(from.getRemoteHostId().toString(), "udp.peer.receiveACKCount", acks.length, 0);
for (int i = 0; i < ackCount; i++) {
long id = data.readACK(i);
if (from.acked(id)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("First full ACK of message " + id + " received from " + from.getRemotePeer());
newAck = true;
//} else if (_log.shouldLog(Log.DEBUG)) {
// _log.debug("Dup full ACK of message " + id + " received from " + from.getRemotePeer());
}
}
} else {
_log.error("Received ACKs with no acks?! " + data);
}
}
if (data.readACKBitfieldsIncluded()) {
ACKBitfield bitfields[] = data.readACKBitfields();
if (bitfields != null) {
rv += bitfields.length;
//_context.statManager().getStatLog().addData(from.getRemoteHostId().toString(), "udp.peer.receivePartialACKCount", bitfields.length, 0);
for (int i = 0; i < bitfields.length; i++) {
if (from.acked(bitfields[i])) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Final partial ACK received: " + bitfields[i] + " from " + from.getRemotePeer());
newAck = true;
} else if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Partial ACK received: " + bitfields[i] + " from " + from.getRemotePeer());
}
}
}
}
if (data.readECN())
from.ECNReceived();
else
from.dataReceived();
// Wake up the packet pusher if it is sleeping.
// By calling add(), this also is a failsafe against possible
// races in OutboundMessageFragments.
if (newAck && from.getOutboundMessageCount() > 0)
_outbound.add(from);
return rv;
}
}