package com.hypixel.hytale.server.core.io;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import com.hypixel.hytale.common.util.FormatUtil;
import com.hypixel.hytale.common.util.NetworkUtil;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.server.core.Constants;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.Options;
import com.hypixel.hytale.server.core.event.events.ShutdownEvent;
import com.hypixel.hytale.server.core.io.commands.BindingsCommand;
import com.hypixel.hytale.server.core.io.handlers.IPacketHandler;
import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler;
import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler;
import com.hypixel.hytale.server.core.io.handlers.game.InventoryPacketHandler;
import com.hypixel.hytale.server.core.io.transport.QUICTransport;
import com.hypixel.hytale.server.core.io.transport.TCPTransport;
import com.hypixel.hytale.server.core.io.transport.Transport;
import com.hypixel.hytale.server.core.io.transport.TransportType;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ServerManager extends JavaPlugin {
public static final PluginManifest MANIFEST = PluginManifest.corePlugin(ServerManager.class).build();
@Nonnull
private static final NetworkUtil.AddressType[] NON_PUBLIC_ADDRESS_TYPES;
private static ServerManager instance;
@Nonnull
private final List<Channel> listeners = new CopyOnWriteArrayList();
@Nonnull
private final List<Function<IPacketHandler, SubPacketHandler>> subPacketHandlers = new ObjectArrayList<Function<IPacketHandler, SubPacketHandler>>();
@Nullable
private Transport transport;
@Nullable
private CompletableFuture<Void> registerFuture;
@Nullable
private CompletableFuture<Void> bootFuture;
public static ServerManager get() {
return instance;
}
public ServerManager(@Nonnull JavaPluginInit init) {
super(init);
instance = this;
if (!Options.getOptionSet().has(Options.BARE)) {
this.init();
}
}
public void init() {
this.registerFuture = CompletableFutureUtil.<Void>_catch(CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> {
long start = System.nanoTime();
Object var10001;
switch ((TransportType)Options.getOptionSet().valuesOf(Options.TRANSPORT).getFirst()) {
case TCP -> var10001 = new TCPTransport();
case QUIC -> var10001 = new QUICTransport();
default -> throw new MatchException((String)null, (Throwable)null);
}
this.transport = (Transport)var10001;
this.getLogger().at(Level.INFO).log("Took %s to setup transport!", FormatUtil.nanosToString(System.nanoTime() - start));
this.registerFuture = null;
})));
}
protected void setup() {
this.getEventRegistry().register((short)-40, ShutdownEvent.class, (event) -> this.unbindAllListeners());
get().registerSubPacketHandlers(InventoryPacketHandler::new);
this.getCommandRegistry().registerCommand(new BindingsCommand());
}
protected void start() {
this.bootFuture = CompletableFuture.runAsync(() -> {
CompletableFuture<Void> registerFuture = this.registerFuture;
if (registerFuture != null) {
registerFuture.getNow((Object)null);
}
if (!HytaleServer.get().isShuttingDown()) {
if (!Options.getOptionSet().has(Options.MIGRATIONS) && !Options.getOptionSet().has(Options.BARE)) {
if (Constants.SINGLEPLAYER) {
try {
InetAddress[] localhosts = InetAddress.getAllByName("localhost");
for(InetAddress localhost : localhosts) {
this.bind(new InetSocketAddress(localhost, ((InetSocketAddress)Options.getOptionSet().valueOf(Options.BIND)).getPort()));
}
} catch (UnknownHostException e) {
throw SneakyThrow.sneakyThrow(e);
}
} else {
for(InetSocketAddress address : Options.getOptionSet().valuesOf(Options.BIND)) {
this.bind(address);
}
if (this.listeners.isEmpty()) {
throw new IllegalArgumentException("Listeners is empty after starting ServerManager!!");
}
}
}
this.bootFuture = null;
}
});
}
protected void shutdown() {
Universe.get().disconnectAllPLayers();
this.unbindAllListeners();
this.transport.shutdown();
this.transport = null;
this.getLogger().at(Level.INFO).log("Finished shutting down ServerManager...");
}
public void unbindAllListeners() {
for(Channel channel : this.listeners) {
this.unbind0(channel);
}
this.listeners.clear();
}
@Nonnull
public List<Channel> getListeners() {
return Collections.unmodifiableList(this.listeners);
}
public boolean bind(@Nonnull InetSocketAddress address) {
if (address.getAddress().isAnyLocalAddress() && this.transport.getType() == TransportType.QUIC) {
Channel channelIpv6 = this.bind0(new InetSocketAddress(NetworkUtil.ANY_IPV6_ADDRESS, address.getPort()));
if (channelIpv6 != null) {
this.listeners.add(channelIpv6);
}
Channel channelIpv4 = this.bind0(new InetSocketAddress(NetworkUtil.ANY_IPV4_ADDRESS, address.getPort()));
if (channelIpv4 != null) {
this.listeners.add(channelIpv4);
}
Channel channelIpv6Localhost = this.bind0(new InetSocketAddress(NetworkUtil.LOOPBACK_IPV6_ADDRESS, address.getPort()));
if (channelIpv6Localhost != null) {
this.listeners.add(channelIpv6Localhost);
}
return channelIpv4 != null || channelIpv6 != null;
} else {
Channel channel = this.bind0(address);
if (channel != null) {
this.listeners.add(channel);
}
return channel != null;
}
}
public boolean unbind(@Nonnull Channel channel) {
boolean success = this.unbind0(channel);
if (success) {
this.listeners.remove(channel);
}
return success;
}
@Nullable
public InetSocketAddress getLocalOrPublicAddress() throws SocketException {
for(Channel channel : this.listeners) {
SocketAddress socketAddress = channel.localAddress();
if (socketAddress instanceof InetSocketAddress inetSocketAddress) {
InetAddress address = inetSocketAddress.getAddress();
if (address.isLoopbackAddress()) {
return inetSocketAddress;
}
if (address.isAnyLocalAddress()) {
InetAddress anyNonLoopbackAddress = NetworkUtil.getFirstNonLoopbackAddress();
if (anyNonLoopbackAddress == null) {
return null;
}
return new InetSocketAddress(anyNonLoopbackAddress, inetSocketAddress.getPort());
}
return inetSocketAddress;
}
}
return null;
}
@Nullable
public InetSocketAddress getNonLoopbackAddress() throws SocketException {
for(Channel channel : this.listeners) {
SocketAddress socketAddress = channel.localAddress();
if (socketAddress instanceof InetSocketAddress inetSocketAddress) {
InetAddress address = inetSocketAddress.getAddress();
if (!address.isLoopbackAddress()) {
if (address.isAnyLocalAddress()) {
InetAddress anyNonLoopbackAddress = NetworkUtil.getFirstNonLoopbackAddress();
if (anyNonLoopbackAddress == null) {
return null;
}
return new InetSocketAddress(anyNonLoopbackAddress, inetSocketAddress.getPort());
}
return inetSocketAddress;
}
}
}
return null;
}
@Nullable
public InetSocketAddress getPublicAddress() throws SocketException {
for(Channel channel : this.listeners) {
SocketAddress socketAddress = channel.localAddress();
if (socketAddress instanceof InetSocketAddress inetSocketAddress) {
InetAddress address = inetSocketAddress.getAddress();
if (!address.isLoopbackAddress() && !address.isSiteLocalAddress()) {
if (address.isAnyLocalAddress()) {
InetAddress anyPublicAddress = NetworkUtil.getFirstAddressWithout(NON_PUBLIC_ADDRESS_TYPES);
if (anyPublicAddress == null) {
return null;
}
return new InetSocketAddress(anyPublicAddress, inetSocketAddress.getPort());
}
return inetSocketAddress;
}
}
}
return null;
}
public void waitForBindComplete() {
CompletableFuture<Void> future = this.bootFuture;
if (future != null) {
future.getNow((Object)null);
}
}
public void registerSubPacketHandlers(@Nonnull Function<IPacketHandler, SubPacketHandler> supplier) {
this.subPacketHandlers.add(supplier);
}
public void populateSubPacketHandlers(@Nonnull GamePacketHandler packetHandler) {
for(Function<IPacketHandler, SubPacketHandler> subPacketHandler : this.subPacketHandlers) {
packetHandler.registerSubPacketHandler((SubPacketHandler)subPacketHandler.apply(packetHandler));
}
}
@Nullable
private Channel bind0(@Nonnull InetSocketAddress address) {
long start = System.nanoTime();
this.getLogger().at(Level.FINE).log("Binding to %s (%s)", address, this.transport.getType());
try {
ChannelFuture f = this.transport.bind(address).sync();
if (f.isSuccess()) {
Channel channel = f.channel();
this.getLogger().at(Level.INFO).log("Listening on %s and took %s", channel.localAddress(), FormatUtil.nanosToString(System.nanoTime() - start));
return channel;
}
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(f.cause()))).log("Could not bind to host %s", address);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted when attempting to bind to host " + String.valueOf(address), e);
} catch (Throwable t) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(t))).log("Failed to bind to %s", address);
}
return null;
}
private boolean unbind0(@Nonnull Channel channel) {
long start = System.nanoTime();
this.getLogger().at(Level.FINE).log("Closing listener %s", channel);
try {
channel.close().await(1L, TimeUnit.SECONDS);
this.getLogger().at(Level.INFO).log("Closed listener %s and took %s", channel, FormatUtil.nanosToString(System.nanoTime() - start));
return true;
} catch (InterruptedException e) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(e)).log("Failed to await for listener to close!");
Thread.currentThread().interrupt();
return false;
}
}
static {
NON_PUBLIC_ADDRESS_TYPES = new NetworkUtil.AddressType[]{NetworkUtil.AddressType.ANY_LOCAL, NetworkUtil.AddressType.LOOPBACK, NetworkUtil.AddressType.SITE_LOCAL, NetworkUtil.AddressType.LINK_LOCAL, NetworkUtil.AddressType.MULTICAST};
}
}