package com.bergerkiller.bukkit.common.internal.network; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import com.bergerkiller.bukkit.common.collections.ClassMap; import com.bergerkiller.bukkit.common.conversion.Conversion; import com.bergerkiller.bukkit.common.events.PacketReceiveEvent; import com.bergerkiller.bukkit.common.events.PacketSendEvent; import com.bergerkiller.bukkit.common.internal.CommonPlugin; import com.bergerkiller.bukkit.common.internal.PacketHandler; import com.bergerkiller.bukkit.common.protocol.CommonPacket; import com.bergerkiller.bukkit.common.protocol.PacketListener; import com.bergerkiller.bukkit.common.protocol.PacketMonitor; import com.bergerkiller.bukkit.common.protocol.PacketType; import com.bergerkiller.bukkit.common.reflection.SafeMethod; import com.bergerkiller.bukkit.common.reflection.classes.EntityPlayerRef; import com.bergerkiller.bukkit.common.reflection.classes.NetworkManagerRef; import com.bergerkiller.bukkit.common.reflection.classes.PlayerConnectionRef; import com.bergerkiller.bukkit.common.utils.CommonUtil; import com.bergerkiller.bukkit.common.utils.PlayerUtil; /** * Basic packet handler implementation for handling packets using a send/receive hook. * The {@link #handlePacketSend(Player, Object, boolean) handlePacketSend(player, packet, wasCancelled)} * and {@link #handlePacketReceive(Player, Object, boolean) handlePacketReceive(player, packet, wasCancelled)} methods should be called * by an additional listener hook. */ public abstract class PacketHandlerHooked implements PacketHandler { private final Map<PacketType, List<PacketListener>> listeners = new HashMap<PacketType, List<PacketListener>>(); private final Map<PacketType, List<PacketMonitor>> monitors = new HashMap<PacketType, List<PacketMonitor>>(); private final Map<Plugin, List<PacketListener>> listenerPlugins = new HashMap<Plugin, List<PacketListener>>(); private final Map<Plugin, List<PacketMonitor>> monitorPlugins = new HashMap<Plugin, List<PacketMonitor>>(); private final ClassMap<SafeMethod<?>> receiverMethods = new ClassMap<SafeMethod<?>>(); @Override public boolean onEnable() { // Initialize all receiver methods Class<?> packetType = PacketType.DEFAULT.getType(); for (Method method : PlayerConnectionRef.TEMPLATE.getType().getDeclaredMethods()) { if (method.getReturnType() != void.class || method.getParameterTypes().length != 1 || !Modifier.isPublic(method.getModifiers())) { continue; } Class<?> arg = method.getParameterTypes()[0]; if (!packetType.isAssignableFrom(arg) || arg == packetType) { continue; } receiverMethods.put(arg, new SafeMethod<Void>(method)); } return true; } @Override public void removePacketListeners(Plugin plugin) { // Listeners List<PacketListener> listeners = listenerPlugins.get(plugin); if (listeners != null) { for (PacketListener listener : listeners) { removePacketListener(listener, false); } } // Monitors List<PacketMonitor> monitors = monitorPlugins.get(plugin); if (monitors != null) { for (PacketMonitor monitor : monitors) { removePacketMonitor(monitor, false); } } } @Override public void removePacketMonitor(PacketMonitor monitor) { removePacketMonitor(monitor, true); } private void removePacketMonitor(PacketMonitor monitor, boolean fromPlugins) { if (monitor == null) { return; } for (List<PacketMonitor> monitorList : monitors.values()) { monitorList.remove(monitor); } if (fromPlugins) { // Remove from plugin list for (Plugin plugin : monitorPlugins.keySet().toArray(new Plugin[0])) { List<PacketMonitor> list = monitorPlugins.get(plugin); // If not null, remove the monitor, if empty afterwards remove the entire entry if (list != null && list.remove(monitor) && list.isEmpty()) { monitorPlugins.remove(plugin); } } } } @Override public void removePacketListener(PacketListener listener) { removePacketListener(listener, true); } private void removePacketListener(PacketListener listener, boolean fromPlugins) { if (listener == null) { return; } for (List<PacketListener> listenerList : listeners.values()) { listenerList.remove(listener); } if (fromPlugins) { // Remove from plugin list for (Plugin plugin : listenerPlugins.keySet().toArray(new Plugin[0])) { List<PacketListener> list = listenerPlugins.get(plugin); // If not null, remove the listener, if empty afterwards remove the entire entry if (list != null && list.remove(listener) && list.isEmpty()) { listenerPlugins.remove(plugin); } } } } @Override public void addPacketMonitor(Plugin plugin, PacketMonitor monitor, PacketType[] types) { if (monitor == null) { throw new IllegalArgumentException("Monitor is not allowed to be null"); } else if (plugin == null) { throw new IllegalArgumentException("Plugin is not allowed to be null"); } // Register the listener for (PacketType type : types) { // Map to listener array List<PacketMonitor> monitorList = monitors.get(type); if (monitorList == null) { monitorList = new ArrayList<PacketMonitor>(); monitors.put(type, monitorList); } monitorList.add(monitor); // Map to plugin list List<PacketMonitor> list = monitorPlugins.get(plugin); if (list == null) { list = new ArrayList<PacketMonitor>(2); monitorPlugins.put(plugin, list); } list.add(monitor); } } @Override public void addPacketListener(Plugin plugin, PacketListener listener, PacketType[] types) { if (listener == null) { throw new IllegalArgumentException("Listener is not allowed to be null"); } else if (plugin == null) { throw new IllegalArgumentException("Plugin is not allowed to be null"); } // Register the listener for (PacketType type : types) { // Map to listener array List<PacketListener> listenerList = listeners.get(type); if (listenerList == null) { listenerList = new ArrayList<PacketListener>(); listeners.put(type, listenerList); } listenerList.add(listener); // Map to plugin list List<PacketListener> list = listenerPlugins.get(plugin); if (list == null) { list = new ArrayList<PacketListener>(2); listenerPlugins.put(plugin, list); } list.add(listener); } } @Override public void receivePacket(Player player, Object packet) { SafeMethod<?> method = this.receiverMethods.get(packet); if (method == null) { CommonPlugin.LOGGER_NETWORK.log(Level.WARNING, "Could not find suitable packet handler for " + packet.getClass().getSimpleName()); } else { method.invoke(getPlayerConnection(player), packet); } } public abstract void sendSilentPacket(Player player, Object packet); @Override public void sendPacket(Player player, Object packet, boolean throughListeners) { Object handle = Conversion.toEntityHandle.convert(player); if (!handle.getClass().equals(CommonUtil.getNMSClass("EntityPlayer"))) { return; } if (!PacketType.DEFAULT.isInstance(packet) || PlayerUtil.isDisconnected(player)) { return; } if (throughListeners) { final Object connection = EntityPlayerRef.playerConnection.get(handle); PlayerConnectionRef.sendPacket(connection, packet); } else { handlePacketSendMonitor(player, PacketType.getType(packet), packet); sendSilentPacket(player, packet); } } @Override public Collection<Plugin> getListening(PacketType type) { List<PacketListener> listenerList = listeners.get(type); if (listenerList == null) { return Collections.emptySet(); } List<Plugin> plugins = new ArrayList<Plugin>(); for (Entry<Plugin, List<PacketListener>> entry : listenerPlugins.entrySet()) { for (PacketListener listener : listenerList) { if (entry.getValue().contains(listener)) { plugins.add(entry.getKey()); break; } } } return plugins; } @Override public void transfer(PacketHandler to) { for (Entry<Plugin, List<PacketListener>> entry : listenerPlugins.entrySet()) { for (PacketListener listener : entry.getValue()) { to.addPacketListener(entry.getKey(), listener, getListenerTypes(listener)); } } for (Entry<Plugin, List<PacketMonitor>> entry : monitorPlugins.entrySet()) { for (PacketMonitor listener : entry.getValue()) { to.addPacketMonitor(entry.getKey(), listener, getMonitorTypes(listener)); } } } protected Object getPlayerConnection(Player player) { return EntityPlayerRef.playerConnection.get(Conversion.toEntityHandle.convert(player)); } private PacketType[] getListenerTypes(PacketListener listener) { ArrayList<PacketType> list = new ArrayList<PacketType>(); for (Map.Entry<PacketType, List<PacketListener>> entry : listeners.entrySet()) { if (entry.getValue().contains(listener)) { list.add(entry.getKey()); } } return Conversion.toObjectArr.convert(list, PacketType.class); } private PacketType[] getMonitorTypes(PacketMonitor listener) { ArrayList<PacketType> list = new ArrayList<PacketType>(); for (Map.Entry<PacketType, List<PacketMonitor>> entry : monitors.entrySet()) { if (entry.getValue().contains(listener)) { list.add(entry.getKey()); } } return Conversion.toObjectArr.convert(list, PacketType.class); } /** * Handles a packet before it is being sent to a player * * @param player for which the packet was meant * @param packet that is handled * @param wasCancelled - True if it was originally cancelled, False if not * @return True if the packet is allowed to be sent, False if not */ public boolean handlePacketSend(Player player, Object packet, boolean wasCancelled) { if(player == null || packet == null) { return true; } // Handle listeners PacketType type = PacketType.getType(packet); List<PacketListener> listenerList = listeners.get(type); if (listenerList != null) { CommonPacket cp = new CommonPacket(packet, type); PacketSendEvent ev = new PacketSendEvent(player, cp); ev.setCancelled(wasCancelled); for (PacketListener listener : listenerList) { listener.onPacketSend(ev); } if (ev.isCancelled()) { return false; } } // Handle monitors handlePacketSendMonitor(player, type, packet); return true; } private void handlePacketSendMonitor(Player player, PacketType packetType, Object packet) { List<PacketMonitor> monitorList = monitors.get(packetType); if (monitorList != null) { CommonPacket cp = new CommonPacket(packet, packetType); for (PacketMonitor monitor : monitorList) { monitor.onMonitorPacketSend(cp, player); } } } /** * Handles a packet before it is being handled by the server * * @param player from which the packet came * @param packet that is handled * @param wasCancelled - True if the packet is allowed to be received, False if not * @return True if the packet is allowed to be received, False if not */ public boolean handlePacketReceive(Player player, Object packet, boolean wasCancelled) { if(player == null || packet == null) { return true; } // Handle listeners PacketType type = PacketType.getType(packet); List<PacketListener> listenerList = listeners.get(type); if (listenerList != null) { CommonPacket cp = new CommonPacket(packet, type); PacketReceiveEvent ev = new PacketReceiveEvent(player, cp); ev.setCancelled(wasCancelled); for (PacketListener listener : listenerList) { listener.onPacketReceive(ev); } if (ev.isCancelled()) { return false; } } // Handle monitors List<PacketMonitor> monitorList = monitors.get(type); if (monitorList != null) { CommonPacket cp = new CommonPacket(packet, type); for (PacketMonitor monitor : monitorList) { monitor.onMonitorPacketReceive(cp, player); } } return true; } protected static long calculatePendingBytes(Player player) { final Object playerHandle = Conversion.toEntityHandle.convert(player); final Object playerConnection = EntityPlayerRef.playerConnection.get(playerHandle); final Object nm = PlayerConnectionRef.networkManager.get(playerConnection); // We can only work on Network manager implementations, INetworkManager implementations are unknown to us if (!NetworkManagerRef.TEMPLATE.isInstance(nm)) { return 0L; } Collection<Object> low = NetworkManagerRef.lowPriorityQueue.get(nm); Collection<Object> high = NetworkManagerRef.highPriorityQueue.get(nm); if (low == null || high == null) { return 0L; } long queuedsize = 0; for (Object p : low) { queuedsize += PacketType.getType(p).getPacketSize(p) + 1; } for (Object p : high) { queuedsize += PacketType.getType(p).getPacketSize(p) + 1; } return queuedsize; } }