1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Updated a few dependencies and made it easier to run on Geyser

This commit is contained in:
Tim203
2023-09-30 17:43:03 +02:00
parent c65edc9218
commit f25673dcaa
43 changed files with 393 additions and 346 deletions

View File

@@ -56,7 +56,7 @@ public class BungeePlatform extends FloodgatePlatform {
}
@Override
protected boolean isProxy() {
public boolean isProxy() {
return true;
}
}

View File

@@ -34,8 +34,8 @@ import jakarta.inject.Named;
import jakarta.inject.Singleton;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.floodgate.core.connection.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
@Factory

View File

@@ -34,9 +34,9 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.api.GeyserApiBase;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.core.connection.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
import org.geysermc.floodgate.core.util.LanguageManager;
import org.geysermc.floodgate.core.util.Utils;

View File

@@ -124,7 +124,7 @@ public abstract class FloodgatePlatform {
context.close();
}
abstract protected boolean isProxy();
abstract public boolean isProxy();
public <T> T getBean(Class<T> clazz) {
return context.getBean(clazz);

View File

@@ -38,7 +38,7 @@ import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.FloodgateHandshakeHandler;
import org.geysermc.floodgate.core.connection.FloodgateHandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.core.connection.HostnameSeparationResult;
import org.geysermc.floodgate.core.crypto.FloodgateDataCodec;
import org.geysermc.floodgate.core.crypto.FloodgateFormatCodec;
import org.geysermc.floodgate.core.util.Constants;
@RequiredArgsConstructor
@@ -76,11 +76,11 @@ public abstract class CommonDataHandler extends ChannelInboundHandlerAdapter {
return;
}
if (separation.headerVersion() != FloodgateDataCodec.VERSION) {
if (separation.headerVersion() != FloodgateFormatCodec.VERSION) {
disablePacketQueue(true);
setKickMessage(String.format(
Constants.UNSUPPORTED_DATA_VERSION,
FloodgateDataCodec.VERSION, separation.headerVersion()
FloodgateFormatCodec.VERSION, separation.headerVersion()
));
return;
}

View File

@@ -82,7 +82,7 @@ public class HandshakeDataImpl implements HandshakeData {
this.javaUniqueId = javaUniqueId;
}
public FloodgateConnection applyChanges(FloodgateConnection connection, String hostname, FloodgateConfig config) {
public FloodgateConnection applyChanges(FloodgateConnection connection, FloodgateConfig config) {
var newLink = !Objects.equals(connection.linkedPlayer(), this.linkedPlayer) ? this.linkedPlayer : null;
var thisIp = convertIp(this.ip);
@@ -94,7 +94,7 @@ public class HandshakeDataImpl implements HandshakeData {
}
var builder = new FloodgateConnectionBuilder(config);
connection.fillBuilder(builder);
// connection.fillBuilder(builder); todo probably remove handshake handlers all together
if (newLink != null) builder.linkedPlayer(newLink);
if (newIp != null) builder.ip(newIp);
return builder.build();

View File

@@ -28,14 +28,14 @@ package org.geysermc.floodgate.core.api;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.nio.charset.StandardCharsets;
import org.geysermc.floodgate.core.crypto.FloodgateDataCodec;
import org.geysermc.floodgate.core.crypto.FloodgateFormatCodec;
import org.geysermc.floodgate.core.scope.ProxyOnly;
import org.geysermc.floodgate.util.BedrockData;
@ProxyOnly
@Singleton
public final class ProxyFloodgateApi extends SimpleFloodgateApi {
@Inject FloodgateDataCodec dataCodec;
@Inject FloodgateFormatCodec dataCodec;
public byte[] createEncryptedData(BedrockData bedrockData) {
try {

View File

@@ -39,10 +39,10 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.core.api.SimpleFloodgateApi;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
import org.geysermc.floodgate.core.connection.audience.ProfileAudienceArgument;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.GlobalPlayerLinking;
import org.geysermc.floodgate.core.link.LinkVerificationException;

View File

@@ -31,7 +31,7 @@ import cloud.commandframework.context.CommandContext;
import jakarta.inject.Singleton;
import org.geysermc.api.Geyser;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
import org.geysermc.floodgate.core.util.Constants;

View File

@@ -34,8 +34,8 @@ import jakarta.inject.Singleton;
import lombok.Getter;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.link.GlobalPlayerLinking;
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;

View File

@@ -38,9 +38,9 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
import org.geysermc.floodgate.core.connection.audience.ProfileAudienceArgument;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.http.xbox.XboxClient;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;

View File

@@ -35,7 +35,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.core.util.Constants;
import org.geysermc.floodgate.core.util.HttpClient;

View File

@@ -35,7 +35,7 @@ import cloud.commandframework.context.CommandContext;
import jakarta.inject.Singleton;
import java.util.Locale;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.core.platform.command.SubCommands;

View File

@@ -33,7 +33,7 @@ import jakarta.inject.Inject;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.core.command.WhitelistCommand.Message;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.core.util.Constants;
import org.geysermc.floodgate.core.util.HttpClient;

View File

@@ -37,7 +37,6 @@ import lombok.Getter;
import org.geysermc.configutils.ConfigUtilities;
import org.geysermc.configutils.file.codec.PathFileCodec;
import org.geysermc.configutils.updater.change.Changes;
import org.geysermc.floodgate.core.crypto.FloodgateDataCodec;
import org.geysermc.floodgate.core.scope.ProxyOnly;
import org.geysermc.floodgate.core.scope.ServerOnly;
import org.geysermc.floodgate.core.util.GlobalBeanCache;
@@ -47,12 +46,10 @@ import org.geysermc.floodgate.core.util.GlobalBeanCache;
@BootstrapContextCompatible
public final class ConfigLoader {
private final Path dataDirectory;
private final FloodgateDataCodec dataCodec;
@Inject
ConfigLoader(@Named("dataDirectory") Path dataDirectory, FloodgateDataCodec dataCodec) {
ConfigLoader(@Named("dataDirectory") Path dataDirectory) {
this.dataDirectory = dataDirectory;
this.dataCodec = dataCodec;
}
@Bean

View File

@@ -27,17 +27,11 @@ package org.geysermc.floodgate.core.connection;
import java.net.InetAddress;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.qual.IntRange;
import org.geysermc.api.Geyser;
import org.geysermc.api.connection.Connection;
import org.geysermc.api.util.BedrockPlatform;
import org.geysermc.api.util.InputMode;
import org.geysermc.api.util.UiProfile;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.floodgate.core.api.legacy.LegacyPlayerWrapper;
@@ -45,87 +39,19 @@ import org.geysermc.floodgate.core.api.legacy.PropertyGlue;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.LinkedPlayer;
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public final class FloodgateConnection implements Connection {
private final String version;
private final String username;
private final UUID identifier;
private final String xuid;
private final String javaUsername;
private final UUID javaUniqueId;
private final BedrockPlatform deviceOs;
private final String languageCode;
private final UiProfile uiProfile;
private final InputMode inputMode;
private final InetAddress ip;
private final LinkedPlayer linkedPlayer;
private final int subscribeId;
private final String verifyCode;
public abstract class FloodgateConnection implements Connection {
private final PropertyGlue propertyGlue = new PropertyGlue();
private LegacyPlayerWrapper legacyPlayer;
@Override
public @NonNull String bedrockUsername() {
return username;
}
public abstract @NonNull UUID identity();
public @NonNull UUID identifier() {
return identifier;
}
public abstract @NonNull InetAddress ip();
@Override
public @NonNull String xuid() {
return xuid;
}
@Override
public @MonotonicNonNull String javaUsername() {
return linkedPlayer != null ? linkedPlayer.getJavaUsername() : javaUsername;
}
@Override
public @MonotonicNonNull UUID javaUuid() {
return linkedPlayer != null ? linkedPlayer.getJavaUniqueId() : javaUniqueId;
}
@Override
public @NonNull String version() {
return version;
}
@Override
public @NonNull BedrockPlatform platform() {
return deviceOs;
}
@Override
public @NonNull String languageCode() {
return languageCode;
}
@Override
public @NonNull UiProfile uiProfile() {
return uiProfile;
}
@Override
public @NonNull InputMode inputMode() {
return inputMode;
}
public @NonNull InetAddress ip() {
return ip;
}
public abstract @MonotonicNonNull LinkedPlayer linkedPlayer();
@Override
public boolean isLinked() {
return linkedPlayer != null;
}
public @Nullable LinkedPlayer linkedPlayer() {
return linkedPlayer;
return linkedPlayer() != null;
}
@Override
@@ -143,29 +69,16 @@ public final class FloodgateConnection implements Connection {
return Geyser.api().transfer(javaUuid(), address, port);
}
public void fillBuilder(FloodgateConnectionBuilder builder) {
builder.version(version)
.username(username)
.identifier(identifier)
.xuid(xuid)
.deviceOs(deviceOs)
.languageCode(languageCode)
.uiProfile(uiProfile)
.inputMode(inputMode)
.ip(ip)
.linkedPlayer(linkedPlayer);
}
public BedrockData toBedrockData() {
return BedrockData.of(
version, username, xuid, deviceOs.ordinal(), languageCode, uiProfile.ordinal(),
inputMode.ordinal(), ip.getHostAddress(), linkedPlayer, false, subscribeId, verifyCode
version(), bedrockUsername(), xuid(), platform().ordinal(), languageCode(), uiProfile().ordinal(),
inputMode().ordinal(), ip().getHostAddress(), linkedPlayer(), false, 0, null
);
}
public LegacyPlayerWrapper legacySelf() {
if (legacyPlayer == null) {
legacyPlayer = new LegacyPlayerWrapper(this, javaUsername, javaUniqueId);
legacyPlayer = new LegacyPlayerWrapper(this, javaUsername(), javaUuid());
}
return legacyPlayer;
}

View File

@@ -41,7 +41,7 @@ public class FloodgateConnectionBuilder {
private final FloodgateConfig config;
private String version;
private String username;
private UUID identifier;
private UUID identity;
private String xuid;
private BedrockPlatform deviceOs;
private String languageCode;
@@ -64,8 +64,8 @@ public class FloodgateConnectionBuilder {
return this;
}
public @This FloodgateConnectionBuilder identifier(UUID identifier) {
this.identifier = Objects.requireNonNull(identifier);
public @This FloodgateConnectionBuilder identity(UUID identity) {
this.identity = Objects.requireNonNull(identity);
return this;
}
@@ -108,10 +108,10 @@ public class FloodgateConnectionBuilder {
// todo add an option to use identity instead of xuid
UUID javaUniqueId = Utils.getJavaUuid(xuid);
return new FloodgateConnection(
return new StandaloneFloodgateConnection(
version,
username,
identifier,
identity,
xuid,
javaUsername(),
javaUniqueId,
@@ -120,9 +120,7 @@ public class FloodgateConnectionBuilder {
uiProfile,
inputMode,
ip,
linkedPlayer,
0,
null
linkedPlayer
);
}

View File

@@ -34,7 +34,6 @@ import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -51,6 +50,8 @@ import org.geysermc.floodgate.core.api.SimpleFloodgateApi;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.codec.FloodgateConnectionCodec;
import org.geysermc.floodgate.core.crypto.FloodgateDataCodec;
import org.geysermc.floodgate.core.crypto.FloodgateFormatCodec;
import org.geysermc.floodgate.core.crypto.exception.UnsupportedVersionException;
import org.geysermc.floodgate.core.link.CommonPlayerLink;
import org.geysermc.floodgate.core.skin.SkinUploadManager;
import org.geysermc.floodgate.core.util.Constants;
@@ -89,7 +90,7 @@ public final class FloodgateHandshakeHandler {
StringBuilder builder = new StringBuilder();
for (String value : hostnameItems) {
int version = FloodgateDataCodec.version(value);
int version = FloodgateFormatCodec.version(value);
if (floodgateData == null && version != -1) {
floodgateData = value;
dataVersion = version;
@@ -110,36 +111,32 @@ public final class FloodgateHandshakeHandler {
@NonNull String floodgateDataString,
@NonNull String hostname
) {
System.out.println("received: " + floodgateDataString);
byte[] floodgateData = floodgateDataString.getBytes(StandardCharsets.UTF_8);
return CompletableFuture.supplyAsync(() -> {
ByteBuffer decoded;
try {
// the actual decryption of the data
decoded = dataCodec.decode(floodgateData);
} catch (InvalidFormatException e) {
// when the Floodgate format couldn't be found
throw callHandlerAndReturnResult(NOT_FLOODGATE_DATA, channel, hostname);
} catch (Exception exception) {
// all the other exceptions are caused by invalid/tempered Floodgate data
if (config.debug()) {
exception.printStackTrace();
}
throw callHandlerAndReturnResult(ResultType.DECRYPT_ERROR, channel, hostname);
}
FloodgateConnection connection;
try {
connection = connectionCodec.decode(decoded);
} catch (Exception exception) {
// todo probably add a format version as that's the most likely reason for this error
// the actual decrypt/verify of the data
connection = dataCodec.decode(floodgateData);
} catch (InvalidFormatException exception) {
// when the Floodgate format couldn't be found
throw callHandlerAndReturnResult(NOT_FLOODGATE_DATA, channel, hostname);
} catch (UnsupportedVersionException exception) {
// unsupported format version
if (config.debug()) {
exception.printStackTrace();
}
throw callHandlerAndReturnResult(INVALID_DATA, channel, hostname);
} catch (Exception exception) {
// all the other exceptions are caused by invalid/tempered Floodgate data
if (config.debug() || true) {
exception.printStackTrace();
}
throw callHandlerAndReturnResult(ResultType.DECRYPT_ERROR, channel, hostname);
}
try {
@@ -206,9 +203,11 @@ public final class FloodgateHandshakeHandler {
// bedrockData.getVerifyCode());
// }
connection = handshakeData.applyChanges(connection, hostname, config);
connection = handshakeData.applyChanges(connection, config);
connectionManager.addConnection(connection);
//todo when splitting up between netty and non-netty make a NettyConnectionManager which
// uses the channel as argument to set the attribute
channel.attr(playerAttribute).set(connection);
return new HandshakeResult(ResultType.SUCCESS, handshakeData, connection);

View File

@@ -0,0 +1,101 @@
package org.geysermc.floodgate.core.connection;
import java.net.InetAddress;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.util.BedrockPlatform;
import org.geysermc.api.util.InputMode;
import org.geysermc.api.util.UiProfile;
import org.geysermc.floodgate.util.LinkedPlayer;
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public final class StandaloneFloodgateConnection extends FloodgateConnection {
private final String version;
private final String username;
private final UUID identity;
private final String xuid;
private final String javaUsername;
private final UUID javaUniqueId;
private final BedrockPlatform deviceOs;
private final String languageCode;
private final UiProfile uiProfile;
private final InputMode inputMode;
private final InetAddress ip;
private final LinkedPlayer linkedPlayer;
@Override
public @NonNull String bedrockUsername() {
return username;
}
public @NonNull UUID identity() {
return identity;
}
@Override
public @NonNull String xuid() {
return xuid;
}
@Override
public @MonotonicNonNull String javaUsername() {
return linkedPlayer != null ? linkedPlayer.getJavaUsername() : javaUsername;
}
@Override
public @MonotonicNonNull UUID javaUuid() {
return linkedPlayer != null ? linkedPlayer.getJavaUniqueId() : javaUniqueId;
}
@Override
public @NonNull String version() {
return version;
}
@Override
public @NonNull BedrockPlatform platform() {
return deviceOs;
}
@Override
public @NonNull String languageCode() {
return languageCode;
}
@Override
public @NonNull UiProfile uiProfile() {
return uiProfile;
}
@Override
public @NonNull InputMode inputMode() {
return inputMode;
}
@Override
public @NonNull InetAddress ip() {
return ip;
}
@Override
public @Nullable LinkedPlayer linkedPlayer() {
return linkedPlayer;
}
public void fillBuilder(FloodgateConnectionBuilder builder) {
builder.version(version)
.username(username)
.identity(identity)
.xuid(xuid)
.deviceOs(deviceOs)
.languageCode(languageCode)
.uiProfile(uiProfile)
.inputMode(inputMode)
.ip(ip)
.linkedPlayer(linkedPlayer);
}
}

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.connection;
package org.geysermc.floodgate.core.connection.audience;
import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext;
import cloud.commandframework.execution.preprocessor.CommandPreprocessor;

View File

@@ -37,7 +37,6 @@ import java.util.Queue;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
import org.geysermc.floodgate.core.platform.util.PlayerType;

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.connection;
package org.geysermc.floodgate.core.connection.audience;
import java.util.Objects;
import java.util.UUID;

View File

@@ -42,6 +42,7 @@ final class CodecUtils {
public static String readString(ByteBuffer buffer) {
var bytes = new byte[readVarInt(buffer)];
buffer.get(bytes);
return new String(bytes, StandardCharsets.UTF_8);
}

View File

@@ -66,7 +66,7 @@ public final class FloodgateConnectionCodec {
private void encode0(FloodgateConnection connection, DataOutputStream stream) throws IOException {
writeString(stream, connection.version());
writeString(stream, connection.bedrockUsername());
writeUniqueId(stream, connection.identifier());
writeUniqueId(stream, connection.identity());
writeUnsignedLong(stream, connection.xuid());
stream.writeByte(connection.platform().ordinal());
writeString(stream, connection.languageCode());
@@ -84,7 +84,7 @@ public final class FloodgateConnectionCodec {
var builder = new FloodgateConnectionBuilder(config)
.version(readString(buffer))
.username(readString(buffer))
.identifier(readUniqueId(buffer))
.identity(readUniqueId(buffer))
.xuid(readUnsignedLong(buffer))
.deviceOs(BedrockPlatform.fromId(buffer.get()))
.languageCode(readString(buffer))

View File

@@ -1,141 +1,29 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.crypto;
import static java.nio.charset.StandardCharsets.UTF_8;
import jakarta.inject.Named;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.Objects;
import org.geysermc.floodgate.core.crypto.topping.Topping;
import org.geysermc.floodgate.core.util.InvalidFormatException;
import jakarta.inject.Singleton;
import java.nio.charset.StandardCharsets;
import org.geysermc.floodgate.core.connection.FloodgateConnection;
import org.geysermc.floodgate.core.connection.codec.FloodgateConnectionCodec;
@Singleton
public final class FloodgateDataCodec {
public static final int VERSION = 2;
public static final byte[] IDENTIFIER = "^Floodgate^".getBytes(UTF_8);
public static final byte[] HEADER = (new String(IDENTIFIER, UTF_8) + (char) (VERSION + 0x3D)).getBytes(UTF_8);
private final FloodgateFormatCodec formatCodec;
private final FloodgateConnectionCodec connectionCodec;
private final DataCodec codec;
private final Topping topping;
public FloodgateDataCodec(
DataCodecType type,
Topping topping,
@Named("dataDirectory") Path dataDirectory
) throws IOException {
Objects.requireNonNull(type);
this.codec = type.dataCodec();
this.topping = topping;
var keyCodecBase = type.keyCodec();
if (type.asymmetrical()) {
var keyPair = ((KeyCodecPair) keyCodecBase).decode(dataDirectory);
((DataCodecKeyPair) codec).init(keyPair);
} else {
var key = ((KeyCodecSingle) keyCodecBase).decode(dataDirectory);
codec.init(key);
}
public FloodgateDataCodec(FloodgateFormatCodec formatCodec, FloodgateConnectionCodec connectionCodec) {
this.formatCodec = formatCodec;
this.connectionCodec = connectionCodec;
}
public static int version(String data) {
if (data.length() < HEADER.length) {
return -1;
public byte[] encode(FloodgateConnection connection) throws Exception {
return formatCodec.encode(connectionCodec.encode(connection));
}
for (int i = 0; i < IDENTIFIER.length; i++) {
if (IDENTIFIER[i] != data.charAt(i)) {
return -1;
}
public String encodeToString(FloodgateConnection connection) throws Exception {
return new String(encode(connection), StandardCharsets.UTF_8);
}
return data.charAt(IDENTIFIER.length) - 0x3D;
}
public byte[] encode(ByteBuffer data) throws Exception {
var encryptedSections = codec.encode(data);
var encodedData = topping.encode(encryptedSections);
return ByteBuffer.allocate(HEADER.length + encodedData.remaining())
.put(HEADER)
.put(encodedData)
.array();
}
public byte[] encodeFromString(String data) throws Exception {
return encode(ByteBuffer.wrap(data.getBytes(UTF_8)));
}
public ByteBuffer decode(byte[] data) throws Exception {
checkHeader(data);
int bufferLength = data.length - HEADER.length;
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER.length, bufferLength);
var encryptedSections = topping.decode(buffer);
return codec.decode(encryptedSections);
}
public String decodeToString(byte[] data) throws Exception {
ByteBuffer decrypted = decode(data);
byte[] decryptedBytes = new byte[decrypted.remaining()];
decrypted.get(decryptedBytes);
return new String(decryptedBytes, UTF_8);
}
public ByteBuffer decodeFromString(String data) throws Exception {
return decode(data.getBytes(UTF_8));
}
/**
* Checks if the header is valid
*
* @param data the data to check
* @throws InvalidFormatException when the header is invalid
*/
public void checkHeader(byte[] data) throws InvalidFormatException {
if (data.length < HEADER.length) {
throw new InvalidFormatException(
"Data length is smaller then header." +
"Needed " + HEADER.length + ", got " + data.length
);
}
for (int i = 0; i < IDENTIFIER.length; i++) {
if (IDENTIFIER[i] != data[i]) {
String identifier = new String(IDENTIFIER, UTF_8);
String received = new String(data, 0, IDENTIFIER.length, UTF_8);
throw new InvalidFormatException(
"Expected identifier " + identifier + ", got " + received
);
}
}
public FloodgateConnection decode(byte[] data) throws Exception {
return connectionCodec.decode(formatCodec.decode(data));
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.core.crypto;
import static java.nio.charset.StandardCharsets.UTF_8;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.Objects;
import org.geysermc.floodgate.core.crypto.exception.UnsupportedVersionException;
import org.geysermc.floodgate.core.crypto.topping.Topping;
import org.geysermc.floodgate.core.util.InvalidFormatException;
@Singleton
public final class FloodgateFormatCodec {
public static final int VERSION = 2;
public static final byte[] IDENTIFIER = "^Floodgate^".getBytes(UTF_8);
public static final byte[] HEADER = (new String(IDENTIFIER, UTF_8) + (char) (VERSION + 0x3D)).getBytes(UTF_8);
private final DataCodec codec;
private final Topping topping;
public FloodgateFormatCodec(
DataCodecType type,
Topping topping,
@Named("dataDirectory") Path dataDirectory
) throws IOException {
Objects.requireNonNull(type);
this.codec = type.dataCodec();
this.topping = topping;
var keyCodecBase = type.keyCodec();
if (type.asymmetrical()) {
var keyPair = ((KeyCodecPair) keyCodecBase).decode(dataDirectory);
((DataCodecKeyPair) codec).init(keyPair);
} else {
var key = ((KeyCodecSingle) keyCodecBase).decode(dataDirectory);
codec.init(key);
}
}
public static int version(String data) {
if (data.length() < HEADER.length) {
return -1;
}
for (int i = 0; i < IDENTIFIER.length; i++) {
if (IDENTIFIER[i] != data.charAt(i)) {
return -1;
}
}
return data.charAt(IDENTIFIER.length) - 0x3D;
}
public byte[] encode(ByteBuffer data) throws Exception {
var encryptedSections = codec.encode(data);
var encodedData = topping.encode(encryptedSections);
return ByteBuffer.allocate(HEADER.length + encodedData.remaining())
.put(HEADER)
.put(encodedData)
.array();
}
public byte[] encodeFromString(String data) throws Exception {
return encode(ByteBuffer.wrap(data.getBytes(UTF_8)));
}
public ByteBuffer decode(byte[] data) throws Exception {
validateHeader(data);
int bufferLength = data.length - HEADER.length;
ByteBuffer buffer = ByteBuffer.wrap(data, HEADER.length, bufferLength);
var encryptedSections = topping.decode(buffer);
return codec.decode(encryptedSections);
}
public String decodeToString(byte[] data) throws Exception {
ByteBuffer decrypted = decode(data);
byte[] decryptedBytes = new byte[decrypted.remaining()];
decrypted.get(decryptedBytes);
return new String(decryptedBytes, UTF_8);
}
public ByteBuffer decodeFromString(String data) throws Exception {
return decode(data.getBytes(UTF_8));
}
/**
* Checks if the header is valid
*
* @param data the data to check
* @throws InvalidFormatException when the header is invalid
*/
public void validateHeader(byte[] data) throws InvalidFormatException, UnsupportedVersionException {
if (data.length < HEADER.length) {
throw new InvalidFormatException(
"Data length is smaller then header." +
"Needed " + HEADER.length + ", got " + data.length
);
}
for (int i = 0; i < IDENTIFIER.length; i++) {
if (IDENTIFIER[i] != data[i]) {
String identifier = new String(IDENTIFIER, UTF_8);
String received = new String(data, 0, IDENTIFIER.length, UTF_8);
throw new InvalidFormatException(
"Expected identifier " + identifier + ", got " + received
);
}
}
var receivedVersion = data[IDENTIFIER.length] - 0x3D;
if (VERSION != receivedVersion) {
throw new UnsupportedVersionException(VERSION, receivedVersion);
}
}
}

View File

@@ -0,0 +1,7 @@
package org.geysermc.floodgate.core.crypto.exception;
public final class UnsupportedVersionException extends Exception {
public UnsupportedVersionException(int expected, int received) {
super("Expected Floodgate data version " + expected + ", received version " + received);
}
}

View File

@@ -41,6 +41,8 @@ import org.geysermc.floodgate.core.database.PlayerLinkRepository;
import org.geysermc.floodgate.core.database.entity.LinkRequest;
import org.geysermc.floodgate.core.database.entity.LinkedPlayer;
@Requires(property = "config.database.enabled", value = "true")
@Requires(property = "config.playerLink.enabled", value = "true")
@Requires(property = "config.playerLink.enableOwnLinking", value = "true")
@Replaces(DisabledPlayerLink.class)
@Named("localLinking")

View File

@@ -31,7 +31,6 @@ import io.micronaut.context.annotation.Factory;
import io.netty.util.AttributeKey;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
@@ -42,7 +41,6 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.geysermc.api.connection.Connection;
import org.geysermc.floodgate.core.crypto.DataCodecType;
import org.geysermc.floodgate.core.crypto.FloodgateDataCodec;
import org.geysermc.floodgate.core.crypto.topping.Base64Topping;
import org.geysermc.floodgate.core.crypto.topping.Topping;
import org.geysermc.floodgate.core.util.Constants;
@@ -91,24 +89,6 @@ public class CommonModule {
return GlobalBeanCache.cacheIfAbsent("topping", Base64Topping::new);
}
@Bean
@BootstrapContextCompatible
@Singleton
public FloodgateDataCodec dataCodec(
DataCodecType type,
Topping topping,
@Named("dataDirectory") Path dataDirectory
) {
//todo find a way to not use bootstrap context, yuk
return GlobalBeanCache.cacheIfAbsent("dataCodec", () -> {
try {
return new FloodgateDataCodec(type, topping, dataDirectory);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
@Bean
@Singleton
@Named("gitBranch")

View File

@@ -38,8 +38,8 @@ import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.GeyserApiBase;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.ProfileAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.util.PlayerType;
import org.geysermc.floodgate.core.util.LanguageManager;
import org.geysermc.floodgate.core.util.Utils;

View File

@@ -29,7 +29,7 @@ import cloud.commandframework.Command;
import cloud.commandframework.CommandManager;
import cloud.commandframework.context.CommandContext;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
/** The base class for every Floodgate command. */
public interface FloodgateCommand {

View File

@@ -27,7 +27,7 @@ package org.geysermc.floodgate.core.platform.command;
import cloud.commandframework.context.CommandContext;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
public abstract class FloodgateSubCommand {
public abstract Class<?> parent();

View File

@@ -30,7 +30,7 @@ import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.Set;
import org.geysermc.floodgate.core.config.FloodgateConfig;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.FloodgateCommand;
/**

View File

@@ -25,7 +25,7 @@
package org.geysermc.floodgate.core.util;
public class InvalidFormatException extends Exception {
public final class InvalidFormatException extends Exception {
public InvalidFormatException(String message) {
super(message);
}

View File

@@ -25,7 +25,7 @@
package org.geysermc.floodgate.core.crypto;
import static org.geysermc.floodgate.core.crypto.FloodgateDataCodec.VERSION;
import static org.geysermc.floodgate.core.crypto.FloodgateFormatCodec.VERSION;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -35,6 +35,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import org.geysermc.floodgate.core.crypto.exception.UnsupportedVersionException;
import org.geysermc.floodgate.core.crypto.topping.Base64Topping;
import org.geysermc.floodgate.core.util.InvalidFormatException;
import org.junit.jupiter.api.Test;
@@ -43,7 +44,7 @@ import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
final class FloodgateDataCodecTest {
final class FloodgateFormatCodecTest {
@ParameterizedTest
@CsvSource({
"^Floodgate^, -1",
@@ -53,7 +54,7 @@ final class FloodgateDataCodecTest {
"^Floodgate^A, 4",
})
void version(ArgumentsAccessor arguments) {
assertEquals(arguments.getInteger(1), FloodgateDataCodec.version(arguments.getString(0)));
assertEquals(arguments.getInteger(1), FloodgateFormatCodec.version(arguments.getString(0)));
}
@Test
@@ -63,7 +64,7 @@ final class FloodgateDataCodecTest {
@Test
void createWithSymmetricalKey() {
assertDoesNotThrow(() -> new FloodgateDataCodec(
assertDoesNotThrow(() -> new FloodgateFormatCodec(
DataCodecType.AES,
new Base64Topping(),
Path.of("src/test/resources/crypto")
@@ -115,31 +116,42 @@ final class FloodgateDataCodecTest {
assertEquals(payloadExpected, payload);
}
@ParameterizedTest
@ValueSource(strings = {
"^Floodgate^" + (VERSION - 1),
"^Floodgate^" + VERSION,
"^Floodgate^" + (VERSION + 1)
})
void headerValid(String value) throws IOException {
var codec = createFloodgateDataCodec();
assertDoesNotThrow(() -> codec.checkHeader(value.getBytes(StandardCharsets.UTF_8)));
}
@ParameterizedTest
@ValueSource(strings = {
"^Floodgate^",
"^Flootgate^="
})
void headerInvalid(String value) throws IOException {
void headerInvalidFormat(String content) throws IOException {
var codec = createFloodgateDataCodec();
assertThrowsExactly(
InvalidFormatException.class,
() -> codec.checkHeader(value.getBytes(StandardCharsets.UTF_8))
() -> codec.validateHeader(content.getBytes(StandardCharsets.UTF_8))
);
}
private FloodgateDataCodec createFloodgateDataCodec() throws IOException {
return new FloodgateDataCodec(DataCodecType.ED25519, new Base64Topping(), Path.of("src/test/resources/crypto"));
@ParameterizedTest
@CsvSource({
(VERSION - 1) + ", false",
VERSION + ", true",
(VERSION + 1) + ", false"
})
void headerVersionValidation(ArgumentsAccessor arguments) throws IOException {
var codec = createFloodgateDataCodec();
var version = arguments.getInteger(0);
var content = ("^Floodgate^" + (char) (version + 0x3D)).getBytes(StandardCharsets.UTF_8);
var valid = arguments.getBoolean(1);
if (valid) {
assertDoesNotThrow(() -> codec.validateHeader(content));
} else {
assertThrowsExactly(UnsupportedVersionException.class, () -> codec.validateHeader(content));
}
}
private FloodgateFormatCodec createFloodgateDataCodec() throws IOException {
return new FloodgateFormatCodec(DataCodecType.ED25519, new Base64Topping(), Path.of("src/test/resources/crypto"));
}
}

View File

@@ -4,4 +4,4 @@ org.gradle.parallel=true
systemProp.org.gradle.unsafe.kotlin.assignment=true
version=2.2.2-SNAPSHOT
micronautVersion=4.0.3
micronautVersion=4.1.3

View File

@@ -1,6 +1,6 @@
[versions]
# parent
micronaut-gradle = "4.0.2"
micronaut-gradle = "4.1.1"
lombok = "8.0.1"
# api
@@ -29,8 +29,8 @@ authlib = "1.5.21"
velocity = "3.1.1"
# buildSrc
indra = "3.0.1"
shadow = "7.1.1"
indra = "3.1.3"
shadow = "8.1.1"
gradle-idea-ext = "1.1.7"
checkerframework = "3.19.0"
@@ -96,7 +96,7 @@ checker-qual = { module = "org.checkerframework:checker-qual", version.ref = "ch
# plugins
indra-common = { module = "net.kyori:indra-common", version.ref = "indra" }
indra-git = { module = "net.kyori:indra-git", version.ref = "indra" }
shadow = { module = "gradle.plugin.com.github.johnrengelman:shadow", version.ref = "shadow" }
shadow = { module = "com.github.johnrengelman:shadow", version.ref = "shadow" }
gradle-idea-ext = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "gradle-idea-ext" }
[plugins]

View File

@@ -69,7 +69,7 @@ public class SpigotPlatform extends FloodgatePlatform {
}
@Override
protected boolean isProxy() {
public boolean isProxy() {
return false;
}
}

View File

@@ -39,8 +39,8 @@ import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.core.command.util.Permission;
import org.geysermc.floodgate.core.connection.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
@Factory

View File

@@ -34,9 +34,9 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.api.GeyserApiBase;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.core.connection.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
import org.geysermc.floodgate.core.util.LanguageManager;

View File

@@ -63,7 +63,7 @@ public class VelocityPlatform extends FloodgatePlatform {
}
@Override
protected boolean isProxy() {
public boolean isProxy() {
return true;
}
}

View File

@@ -35,8 +35,8 @@ import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.geysermc.floodgate.core.connection.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.audience.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
@Factory

View File

@@ -36,9 +36,9 @@ import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.api.GeyserApiBase;
import org.geysermc.floodgate.core.connection.UserAudience;
import org.geysermc.floodgate.core.connection.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.core.connection.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.core.connection.audience.UserAudience.PlayerAudience;
import org.geysermc.floodgate.core.platform.command.CommandUtil;
import org.geysermc.floodgate.core.util.LanguageManager;
import org.geysermc.floodgate.core.util.Utils;