package speedytools.common.network.multipart; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; import speedytools.common.network.PacketSender; import speedytools.common.utilities.ErrorLog; import java.util.TreeSet; /** * User: The Grey Ghost * Date: 31/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 MultipartProtocols.txt and MultipartPacket.png * The MultipartOneAtATimeReceiver and MultipartOneAtATimeSender are designed to only handle one of each * type of multipartpacket at a time (eg Selection, MapData, etc). If another packet is sent before the first is * finished, the first packet is aborted. * Usage: * (1) Instantiate MultipartOneAtATimeReceiver * (2) setPacketSender with the appropriate PacketSender (wrapper for either a NetClientHandler or NetServerHandler) * (3) registerPacketCreator to register your PacketCreator. The PacketCreator takes an incoming Packet250CustomPayload * and turns it into the appropriate MultipartPacket class. * (4) registerLinkageFactory to register your LinkageFactory. The LinkageFactory is used to create an appropriate * PacketLinkage, used to inform the receiving class about the progress of the transmission. * (5) When incoming packets arrive, process them with processIncomingPacket * (6) Periodically (eg once per second) call onTick() to handle timeouts * (7) To abort an incoming packet, call abortPacket */ public class MultipartOneAtATimeReceiver { public MultipartOneAtATimeReceiver() { packetCreatorRegistry = null; packetLinkageFactory = null; packetBeingReceived = null; newestOldPacketID = MultipartPacket.NULL_PACKET_ID; abortedPacketIDs = new TreeSet<Integer>(); completedPacketIDs = new TreeSet<Integer>(); incomingPacketHandler = this.new IncomingPacketHandler(); // Packet250MultipartSegment.registerHandler(packetHandlerRegistry, incomingPacketHandler, side, acknowledgementPacketType); // registerPacketCreator(packetCreator); } /** * Changes to a new PacketSender * @param newPacketSender */ public void setPacketSender(PacketSender newPacketSender) { packetSender = newPacketSender; } private void doAbortPacket() { PacketTransmissionInfo pti = packetBeingReceived; pti.linkage.packetAborted(); Packet250MultipartSegmentAcknowledge abortPacket = pti.packet.getReceiverAbortPacket(); packetSender.sendPacket(abortPacket); int packetUniqueID = pti.packet.getUniqueID(); assert newestOldPacketID <= packetUniqueID; newestOldPacketID = packetUniqueID; packetBeingReceived = null; abortedPacketIDs.add(packetUniqueID); } private static final int ACKNOWLEDGEMENT_WAIT_MS = 100; // minimum ms elapsed between sending a packet and expecting an acknowledgement private static final int MS_TO_NS = 1000000; /** * processes an incoming packet; creates a new MultipartPacket if necessary (calling the LinkageFactory), * informs the appropriate linkage of progress * @return true for success, false if packet is invalid or is ignored */ public class IncomingPacketHandler implements Packet250MultipartSegment.PacketHandlerMethod { /** * processes an incoming packet; * informs the appropriate linkage of progress * sends an abort packet back if this packet has already been completed * @return true for success, false if packet is invalid or is ignored */ public boolean handlePacket(Packet250MultipartSegment packet, MessageContext ctx) { return processIncomingPacket(packet); } } public boolean processIncomingPacket(Packet250MultipartSegment packet) { int packetUniqueID = packet.getUniqueMultipartID(); // If this is an old packet; // resend our acknowledgement (either abort, or full acknowledge), or ignore if we don't recognise it if (packetUniqueID <= newestOldPacketID) { if (abortedPacketIDs.contains(packetUniqueID)) { Packet250MultipartSegmentAcknowledge abortPacket = MultipartPacket.getAbortPacketForLostPacket(packet, true); if (abortPacket != null) packetSender.sendPacket(abortPacket); } else if (completedPacketIDs.contains(packetUniqueID)) { Packet250MultipartSegmentAcknowledge fullAckPacket = MultipartPacket.getFullAcknowledgePacketForLostPacket(packet); if (fullAckPacket != null) packetSender.sendPacket(fullAckPacket); } return false; } // if we're already receiving this packet, process it // otherwise, create a new one, aborting the packet in progress if there is one if (packetBeingReceived != null) { if (packetBeingReceived.packet.getUniqueID() == packetUniqueID) { PacketTransmissionInfo pti = packetBeingReceived; boolean success = doProcessIncoming(pti, packet); if (success) pti.linkage.progressUpdate(pti.packet.getPercentComplete()); return success; } doAbortPacket(); } if (packetCreatorRegistry == null) { ErrorLog.defaultLog().info("Received packet but no corresponding PacketCreator in registry"); return false; } if (packetLinkageFactory == null) { ErrorLog.defaultLog().info("Received packet but no corresponding LinkageFactory in registry"); return false; } MultipartPacket newPacket = packetCreatorRegistry.createNewPacket(packet); if (newPacket == null) { // something wrong! send abort packet back Packet250MultipartSegmentAcknowledge abortPacket = MultipartPacket.getAbortPacketForLostPacket(packet, true); packetSender.sendPacket(abortPacket); return false; } PacketLinkage newLinkage = packetLinkageFactory.createNewLinkage(newPacket); if (newLinkage == null) return false; PacketTransmissionInfo packetTransmissionInfo = new PacketTransmissionInfo(); packetTransmissionInfo.packet = newPacket; packetTransmissionInfo.linkage = newLinkage; packetTransmissionInfo.transmissionState = PacketTransmissionInfo.TransmissionState.RECEIVING; packetTransmissionInfo.timeOfLastAction = System.nanoTime(); assert (packetBeingReceived == null); packetBeingReceived = packetTransmissionInfo; boolean success = doProcessIncoming(packetTransmissionInfo, packet); if (success) newLinkage.progressUpdate(newPacket.getPercentComplete()); return success; } private boolean doProcessIncoming(PacketTransmissionInfo packetTransmissionInfo, Packet250MultipartSegment packet) { boolean success = packetTransmissionInfo.packet.processIncomingSegment(packet); if ( packetTransmissionInfo.packet.hasBeenAborted() || packetTransmissionInfo.packet.allSegmentsReceived()) { int uniqueID = packetTransmissionInfo.packet.getUniqueID(); if (packetTransmissionInfo.packet.hasBeenAborted()) { abortedPacketIDs.add(uniqueID); packetTransmissionInfo.linkage.packetAborted(); } else { completedPacketIDs.add(uniqueID); packetTransmissionInfo.linkage.packetCompleted(); Packet250MultipartSegmentAcknowledge ackPacket = packetBeingReceived.packet.getAcknowledgementPacket(); packetSender.sendPacket(ackPacket); } packetBeingReceived = null; newestOldPacketID = uniqueID; } else { packetTransmissionInfo.linkage.progressUpdate(packetTransmissionInfo.packet.getPercentComplete()); Packet250MultipartSegmentAcknowledge ackPacket = packetBeingReceived.packet.getAcknowledgementPacket(); packetSender.sendPacket(ackPacket); } return success; } private static final long TIMEOUT_AGE_S = 120; // discard "stale" packets older than this - also abort packets and completed receive packets private static final long NS_TO_S = 1000 * 1000 * 1000; private static final int MAX_ABORTED_PACKET_COUNT = 100; // retain this many aborted packet IDs private static final int MAX_COMPLETED_PACKET_COUNT = 100; // retain this many completed packet IDs /** * should be called frequently to handle packet maintenance tasks, especially timeouts */ public void onTick() { while (abortedPacketIDs.size() > MAX_ABORTED_PACKET_COUNT) abortedPacketIDs.pollFirst(); while (completedPacketIDs.size() > MAX_COMPLETED_PACKET_COUNT) completedPacketIDs.pollFirst(); long timeNow = System.nanoTime(); long discardTime = timeNow - TIMEOUT_AGE_S * NS_TO_S; // discard incoming packet if it hasn't been updated for a long time PacketTransmissionInfo pti = packetBeingReceived; if (pti != null && pti.timeOfLastAction < discardTime) { Packet250MultipartSegmentAcknowledge packet = pti.packet.getReceiverAbortPacket(); packetSender.sendPacket(packet); abortedPacketIDs.add(pti.packet.getUniqueID()); pti.linkage.packetAborted(); packetBeingReceived = null; } } public void abortPacket(PacketLinkage linkage) { if (packetBeingReceived == null) return; if (packetBeingReceived.linkage.getPacketID() != linkage.getPacketID()) return; doAbortPacket(); } public void registerPacketCreator(MultipartPacket.MultipartPacketCreator packetCreator) { packetCreatorRegistry = packetCreator; } public void registerLinkageFactory(PacketReceiverLinkageFactory linkageFactory) { packetLinkageFactory = linkageFactory; } /** * This class is used by the MultipartPacketHandler to communicate the packet transmission progress to the sender */ public interface PacketLinkage { public void progressUpdate(int percentComplete); public void packetCompleted(); public void packetAborted(); public int getPacketID(); } /** * The Factory creates a new linkage, which will be used to communicate the packet receiving progress to the recipient */ public interface PacketReceiverLinkageFactory { public PacketLinkage createNewLinkage(MultipartPacket linkedPacket); } private static class PacketTransmissionInfo { public MultipartPacket packet; public PacketLinkage linkage; public long timeOfLastAction; public TransmissionState transmissionState; public enum TransmissionState {RECEIVING, SENDING_INITIAL_SEGMENTS, SENDER_WAITING_FOR_ACK, WAITING_FOR_FIRST_RESEND, RESENDING}; } private MultipartPacket.MultipartPacketCreator packetCreatorRegistry; private PacketReceiverLinkageFactory packetLinkageFactory; private PacketTransmissionInfo packetBeingReceived; private Integer newestOldPacketID; private TreeSet<Integer> abortedPacketIDs; private TreeSet<Integer> completedPacketIDs; private PacketSender packetSender; private IncomingPacketHandler incomingPacketHandler; }