/* * Minecraft Forge * Copyright (c) 2016-2018. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.fml.network; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.network.NetworkManager; import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.network.event.EventNetworkChannel; import net.minecraftforge.fml.network.simple.SimpleChannel; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import java.util.*; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; /** * The network registry. Tracks channels on behalf of mods. */ public class NetworkRegistry { private static final Logger LOGGER = LogManager.getLogger(); private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY"); private static Map instances = new HashMap<>(); /** * Special value for clientAcceptedVersions and serverAcceptedVersions predicates indicating the other side lacks * this channel. */ @SuppressWarnings("RedundantStringConstructorCall") public static String ABSENT = new String("ABSENT \uD83E\uDD14"); public static List getNonVanillaNetworkMods() { return instances.keySet().stream().map(Object::toString).collect(Collectors.toList()); } static boolean acceptsVanillaConnections() { return instances.isEmpty(); } /** * Create a new {@link SimpleChannel}. * * @param name The registry name for this channel. Must be unique * @param networkProtocolVersion The network protocol version string that will be offered to the remote side {@link ChannelBuilder#networkProtocolVersion(Supplier)} * @param clientAcceptedVersions Called on the client with the networkProtocolVersion string from the server {@link ChannelBuilder#clientAcceptedVersions(Predicate)} * @param serverAcceptedVersions Called on the server with the networkProtocolVersion string from the client {@link ChannelBuilder#serverAcceptedVersions(Predicate)} * @return A new {@link SimpleChannel} * * @see ChannelBuilder#newSimpleChannel(ResourceLocation, Supplier, Predicate, Predicate) */ public static SimpleChannel newSimpleChannel(final ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { return new SimpleChannel(createInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions)); } /** * Create a new {@link EventNetworkChannel}. * * @param name The registry name for this channel. Must be unique * @param networkProtocolVersion The network protocol version string that will be offered to the remote side {@link ChannelBuilder#networkProtocolVersion(Supplier)} * @param clientAcceptedVersions Called on the client with the networkProtocolVersion string from the server {@link ChannelBuilder#clientAcceptedVersions(Predicate)} * @param serverAcceptedVersions Called on the server with the networkProtocolVersion string from the client {@link ChannelBuilder#serverAcceptedVersions(Predicate)} * @return A new {@link EventNetworkChannel} * * @see ChannelBuilder#newEventChannel(ResourceLocation, Supplier, Predicate, Predicate) */ public static EventNetworkChannel newEventChannel(final ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { return new EventNetworkChannel(createInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions)); } /** * Creates the internal {@link NetworkInstance} that tracks the channel data. * @param name registry name * @param networkProtocolVersion The protocol version string * @param clientAcceptedVersions The client accepted predicate * @param serverAcceptedVersions The server accepted predicate * @return The {@link NetworkInstance} * @throws IllegalArgumentException if the name already exists */ private static NetworkInstance createInstance(ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { final NetworkInstance networkInstance = new NetworkInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions); if (instances.containsKey(name)) { LOGGER.error(NETREGISTRY, "NetworkDirection channel {} already registered.", name); throw new IllegalArgumentException("NetworkDirection Channel {"+ name +"} already registered"); } instances.put(name, networkInstance); return networkInstance; } /** * Find the {@link NetworkInstance}, if possible * * @param resourceLocation The network instance to lookup * @return The {@link Optional} {@link NetworkInstance} */ static Optional findTarget(ResourceLocation resourceLocation) { return Optional.ofNullable(instances.get(resourceLocation)); } /** * Construct the NBT representation of the channel list, for use during login handshaking * * @see FMLHandshakeMessages.S2CModList * @see FMLHandshakeMessages.C2SModListReply * * @return An nbt tag list */ static NBTTagList buildChannelVersions() { return instances.entrySet().stream().map(e-> { final NBTTagCompound tag = new NBTTagCompound(); tag.setString("name", e.getKey().toString()); tag.setString("version", e.getValue().getNetworkProtocolVersion()); return tag; }).collect(Collectors.toCollection(NBTTagList::new)); } /** * Validate the channels from the server on the client. Tests the client predicates against the server * supplied network protocol version. * * @param channels An @{@link NBTTagList} of name->version pairs for testing * @return true if all channels accept themselves */ static boolean validateClientChannels(final NBTTagList channels) { return validateChannels(channels, "server", NetworkInstance::tryServerVersionOnClient); } /** * Validate the channels from the client on the server. Tests the server predicates against the client * supplied network protocol version. * @param channels An @{@link NBTTagList} of name->version pairs for testing * @return true if all channels accept themselves */ static boolean validateServerChannels(final NBTTagList channels) { return validateChannels(channels, "client", NetworkInstance::tryClientVersionOnServer); } /** * Tests if the nbt list matches with the supplied predicate tester * * @param channels An @{@link NBTTagList} of name->version pairs for testing * @param originName A label for use in logging (where the version pairs came from) * @param testFunction The test function to use for testing * @return true if all channels accept themselves */ private static boolean validateChannels(final NBTTagList channels, final String originName, BiFunction testFunction) { Map incoming = channels.stream().map(NBTTagCompound.class::cast).collect(Collectors.toMap(tag->new ResourceLocation(tag.getString("name")),tag->tag.getString("version"))); final List> results = instances.values().stream(). map(ni -> { final String incomingVersion = incoming.getOrDefault(ni.getChannelName(), ABSENT); final boolean test = testFunction.apply(ni, incomingVersion); LOGGER.debug(NETREGISTRY, "Channel '{}' : Version test of '{}' from {} : {}", ni.getChannelName(), incomingVersion, originName, test ? "ACCEPTED" : "REJECTED"); return Pair.of(ni.getChannelName(), test); }).filter(p->!p.getRight()).collect(Collectors.toList()); if (!results.isEmpty()) { LOGGER.error(NETREGISTRY, "Channels [{}] rejected their {} side version number", results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(",")), originName); return false; } LOGGER.debug(NETREGISTRY, "Accepting channel list from {}", originName); return true; } /** * Retrieve the {@link LoginPayload} list for dispatch during {@link FMLHandshakeHandler#tickLogin(NetworkManager)} handling. * Dispatches {@link net.minecraftforge.fml.network.NetworkEvent.GatherLoginPayloadsEvent} to each {@link NetworkInstance}. * * @return The {@link LoginPayload} list */ static List gatherLoginPayloads() { List gatheredPayloads = new ArrayList<>(); instances.values().forEach(ni->ni.dispatchGatherLogin(gatheredPayloads)); return gatheredPayloads; } /** * Tracks individual outbound messages for dispatch to clients during login handling. Gathered by dispatching * {@link net.minecraftforge.fml.network.NetworkEvent.GatherLoginPayloadsEvent} during early connection handling. */ public static class LoginPayload { /** * The data for sending */ private final PacketBuffer data; /** * A channel which will receive a {@link NetworkEvent.LoginPayloadEvent} from the {@link FMLLoginWrapper} */ private final ResourceLocation channelName; /** * Some context for logging purposes */ private final String messageContext; public LoginPayload(final PacketBuffer buffer, final ResourceLocation channelName, final String messageContext) { this.data = buffer; this.channelName = channelName; this.messageContext = messageContext; } public PacketBuffer getData() { return data; } public ResourceLocation getChannelName() { return channelName; } public String getMessageContext() { return messageContext; } } /** * Builder for constructing network channels using a builder style API. */ public static class ChannelBuilder { private ResourceLocation channelName; private Supplier networkProtocolVersion; private Predicate clientAcceptedVersions; private Predicate serverAcceptedVersions; /** * The name of the channel. Must be unique. * @param channelName The name of the channel * @return the channel builder */ public static ChannelBuilder named(ResourceLocation channelName) { ChannelBuilder builder = new ChannelBuilder(); builder.channelName = channelName; return builder; } /** * The network protocol string for this channel. This will be gathered during login and sent to * the remote partner, where it will be tested with against the relevant predicate. * * @see #serverAcceptedVersions(Predicate) * @see #clientAcceptedVersions(Predicate) * @param networkProtocolVersion A supplier of strings for network protocol version testing * @return the channel builder */ public ChannelBuilder networkProtocolVersion(Supplier networkProtocolVersion) { this.networkProtocolVersion = networkProtocolVersion; return this; } /** * A predicate run on the client, with the {@link #networkProtocolVersion(Supplier)} string from * the server, or the special value {@link NetworkRegistry#ABSENT} indicating the absence of * the channel on the remote side. * @param clientAcceptedVersions A predicate for testing * @return the channel builder */ public ChannelBuilder clientAcceptedVersions(Predicate clientAcceptedVersions) { this.clientAcceptedVersions = clientAcceptedVersions; return this; } /** * A predicate run on the server, with the {@link #networkProtocolVersion(Supplier)} string from * the server, or the special value {@link NetworkRegistry#ABSENT} indicating the absence of * the channel on the remote side. * @param serverAcceptedVersions A predicate for testing * @return the channel builder */ public ChannelBuilder serverAcceptedVersions(Predicate serverAcceptedVersions) { this.serverAcceptedVersions = serverAcceptedVersions; return this; } /** * Create the network instance * @return the {@link NetworkInstance} */ private NetworkInstance createNetworkInstance() { return createInstance(channelName, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions); } /** * Build a new {@link SimpleChannel} with this builder's configuration. * * @return A new {@link SimpleChannel} */ public SimpleChannel simpleChannel() { return new SimpleChannel(createNetworkInstance()); } /** * Build a new {@link EventNetworkChannel} with this builder's configuration. * @return A new {@link EventNetworkChannel} */ public EventNetworkChannel eventNetworkChannel() { return new EventNetworkChannel(createNetworkInstance()); } } }