package com.gmail.woodyc40.common.protocol.impl; import com.gmail.woodyc40.common.protocol.*; import com.gmail.woodyc40.common.protocol.events.PacketReceivedEvent; import com.gmail.woodyc40.common.protocol.events.PacketSentEvent; import com.gmail.woodyc40.common.protocol.events.ProtocolEvent; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import net.tridentsdk.Trident; import net.tridentsdk.docs.InternalUseOnly; import net.tridentsdk.entity.living.Player; import net.tridentsdk.event.Listener; import net.tridentsdk.event.player.PlayerDisconnectEvent; import net.tridentsdk.event.player.PlayerJoinEvent; import net.tridentsdk.factory.Factories; import net.tridentsdk.meta.MessageBuilder; import net.tridentsdk.plugin.TridentPluginHandler; import net.tridentsdk.reflect.FastField; import net.tridentsdk.server.TridentServer; import net.tridentsdk.server.netty.ClientConnection; import net.tridentsdk.server.netty.packet.PacketDirection; import net.tridentsdk.server.netty.protocol.Protocol; import net.tridentsdk.server.player.TridentPlayer; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.StampedLock; /** * Implements {@link com.gmail.woodyc40.common.protocol.ProtocolManagement} * * <p>To obtain the instance of this manager, use this code: * <br/> * {@code ProtocolManagement management = ProtocolManagement.manager();}</p> * * <p>All methods in this class are thread safe</p> * * @author Pierre C */ @ThreadSafe public final class ManagementImpl implements ProtocolManagement, Listener { /** The channel of a player's connection to the server */ private static final FastField CHANNEL_FIELD = Factories.reflect().field(ClientConnection.class, "channel"); /** The map of protocol listeners */ @GuardedBy("lock") private final Map<Class<? extends ProtocolEvent>, List<ProtocolListener<?>>> listeners = Maps.newHashMap(); /** The lock used to guard the listeners in order to ensure thread safety */ private final StampedLock lock = new StampedLock(); /** The channels associated with the players */ private final Map<Player, Channel> channelMap = new ConcurrentHashMap<>(); // The listener map MUST use a lock // The operations upon the map, in which a cache like system is used // requires that the entire operation completes before the next begins. // Unfortunately, when using a non-blocking, or concurrent solution, // entries may become "leaked" when one write creates a new list and // does not place the new list in before another write occurs, in // which case, the first is lost. // To put this into perspective: // // T1 -> Get EVENT = null -> Create new list and insert new listener -> reinsert new list // T2 ------------------> Get EVENT = null -> Create new list and insert new listener -> reinsert new list // // The listener placed in T1 was subsequently removed by T2 as the key // was assigned to a new instance of a list. // It is therefore necessary to lock the entire operation between // obtaining the event, and placing the list back into the listener map. /** * Do not instantiate this class, you will get an error * * <p>To obtain the instance of this manager, use this code: * <br/> * {@code ProtocolManagement management = ProtocolManagement.manager();}</p> */ @InternalUseOnly public ManagementImpl() { Class<?> caller = Trident.findCaller(3); Class<?> caller1 = Trident.findCaller(6); // Ensure it isn't the real thing or the plugin manager if (!caller.equals(ProtocolManagement.class)) { if (!caller1.equals(TridentPluginHandler.class)) { throw new UnsupportedOperationException("Use ProtocolManagement.manager() instead"); } } new Thread(() -> { while (true) { Packet packet = createPacket(0x02, true); Object pos = null; try { pos = Enum.valueOf((Class<Enum>) Class.forName("net.tridentsdk.server.packets.play.out.PacketPlayOutChat$ChatPosition"), "CHAT"); } catch (ClassNotFoundException e) { e.printStackTrace(); } packet.write(new MessageBuilder("Hello World").build().asJson()); packet.write(pos); final Packet finalPacket = packet; send(pipe -> { finalPacket.encode(pipe); return TridentPlayer.players(); }); } }).start(); } @Override public <T extends ProtocolEvent> void intercept(ProtocolListener<T> listener) { Class<T> eventType = (Class<T>) new TypeToken<T>(listener.getClass()) {}.getRawType(); long stamp = lock.writeLock(); try { List<ProtocolListener<?>> listenerQueue = listeners.get(eventType); if (listenerQueue == null) { listenerQueue = Lists.newArrayList(); } listenerQueue.add(listener); listeners.put(eventType, listenerQueue); } finally { lock.unlockWrite(stamp); } } @Override public <T extends ProtocolEvent> T transmit(T event, ProtocolAction action) { Class<T> eventType = (Class<T>) new TypeToken<T>(event.getClass()) {}.getRawType(); long stamp = lock.readLock(); List<ProtocolListener<?>> listenerQueue; try { listenerQueue = listeners.get(eventType); } finally { lock.unlockRead(stamp); } if (listenerQueue == null) { return event; } if (action == ProtocolAction.NO_OP) { for (ProtocolListener<?> listener : listenerQueue) { ((ProtocolListener<T>) listener).intercept(event); } } else { send(action); for (ProtocolListener<?> listener : listenerQueue) { ((ProtocolListener<T>) listener).intercept(event); } } return event; } @Override public <T extends ProtocolEvent> void remove(ProtocolListener<T> listener) { Class<T> eventType = (Class<T>) new TypeToken<T>(listener.getClass()) {}.getRawType(); long stamp = lock.writeLock(); try { List<ProtocolListener<?>> listenerQueue = listeners.get(eventType); if (listenerQueue == null) { throw new IllegalArgumentException("No such listener: " + listener.getClass()); } if (!listenerQueue.remove(listener)) { throw new IllegalArgumentException("No such listener: " + listener.getClass()); } listeners.put(eventType, listenerQueue); } finally { lock.unlockWrite(stamp); } } @Override public void send(ProtocolAction action) { ByteBuf byteBuf = Unpooled.directBuffer(); ByteAppender appender = ByteAppender.wrap(byteBuf); Collection<Player> targets = action.handle(appender); if (targets != null) { for (Player target : targets) { if (target == null) { throw new IllegalArgumentException("Cannot process null player"); } Channel channel = channelMap.get(target); if (channel != null) { appender.writeTo(channel); } else { appender.writeTo(inject(target)); } } } else { throw new IllegalArgumentException("Did you mean new Player[0]?"); } } @Override public void send(ByteAppender appender, Collection<Player> players) { for (Player target : players) { Channel channel = channelMap.get(target); if (channel != null) { appender.writeTo(channel); } else { appender.writeTo(inject(target)); } } } @Override public void sendAll(ProtocolAction action) { ByteBuf byteBuf = Unpooled.directBuffer(); ByteAppender appender = ByteAppender.wrap(byteBuf); action.handle(appender); send(appender, TridentPlayer.players()); } @Override public Packet createPacket(int id, boolean out) { return new PacketImpl(TridentServer .instance() .protocol() .getPacket(id, Protocol.ClientStage.PLAY, out ? PacketDirection.OUT : PacketDirection.IN)); } private Channel inject(Player player) { TridentPlayer tridentPlayer = (TridentPlayer) player; ClientConnection connection = tridentPlayer.connection(); // This only works because it's protected ;) Channel channel = CHANNEL_FIELD.get(connection); channel.pipeline().addBefore("PacketHandler#0", tridentPlayer.uniqueId().toString() + " interceptor", new ChannelHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { PacketReceivedEvent event = transmit(new PacketReceivedEvent(PacketImpl.in(msg), tridentPlayer), ProtocolAction.NO_OP); if (!event.ignored()) { super.channelRead(ctx, msg); } } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { PacketSentEvent event = transmit(new PacketSentEvent(PacketImpl.out(msg), tridentPlayer), ProtocolAction.NO_OP); if (!event.ignored()) { super.write(ctx, msg, promise); } } }); channelMap.put(player, channel); return channel; } public void playerJoin(PlayerJoinEvent event) { inject(event.player()); } public void playerLeave(PlayerDisconnectEvent event) { Channel channel = channelMap.get(event.player()); if (channel != null) { channel.pipeline().remove(event.player().uniqueId().toString() + " interceptor"); } } }