mirror of
https://github.com/layue13/MoonBridge.git
synced 2026-10-26 19:03:49 +00:00
- 控制通道:注册字段长度上限统一放在 messaging 的 ControlHandshakeLimits,后端启动时 按代理的上限校验,不再出现后端接受、代理每次重连都拒绝的配置;后端按阶段记录连接、 注册与断线失败(每类只记一次,不含对端数据),配置错误时输出具体缺失的键 - 转服登录:握手与 Login Start 的写入合并为 SessionChannels.writeBackendLogin, Login Start 无法编码时关闭通道释放已排队的握手,不再泄漏缓冲区 - 命令:插件提供的 CommandSource 的 player()/reply() 不再在宿主锁内调用 - 配置:插件条目没有设置时视为空设置而不是空指针;--validate-config 也检查同一后端名的 密钥冲突;地址解析不再做 DNS,后端地址按目录使用的 URI 规则校验 - WebSocket:IPv4 映射写法(::ffff:a.b.c.d)的可信代理现在能匹配 IPv4 对端 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
4.6 KiB
Java
94 lines
4.6 KiB
Java
package dev.moonbridge.bukkit;
|
|
|
|
import dev.moonbridge.backendchannel.BackendChannelClient;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.Listener;
|
|
import org.bukkit.event.server.PluginDisableEvent;
|
|
import org.bukkit.plugin.Plugin;
|
|
import org.bukkit.plugin.ServicePriority;
|
|
import org.bukkit.plugin.java.JavaPlugin;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Arrays;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.RejectedExecutionException;
|
|
|
|
/** One process-level authenticated transport shared by all dependent plugins. */
|
|
public final class MoonBridgeBackendPlugin extends JavaPlugin implements Listener {
|
|
private volatile boolean stopping;
|
|
private BackendChannelClient client;
|
|
private OwnedMessagingService service;
|
|
private OwnedBukkitSessionService sessions;
|
|
|
|
@Override public void onEnable() {
|
|
stopping = false;
|
|
saveDefaultConfig();
|
|
byte[] secret = null;
|
|
try {
|
|
String secretText = getConfig().getString("secret", "");
|
|
if (secretText.startsWith("replace-") || secretText.getBytes(StandardCharsets.UTF_8).length < 32) {
|
|
throw new IllegalArgumentException("Configure a unique secret of at least 32 UTF-8 bytes");
|
|
}
|
|
secret = secretText.getBytes(StandardCharsets.UTF_8);
|
|
client = new BackendChannelClient(
|
|
required("proxyHost"), getConfig().getInt("proxyPort", 28081),
|
|
required("instanceId"), required("backendName"), required("gameAddress"),
|
|
UUID.randomUUID().toString(), required("keyId"), secret);
|
|
service = new OwnedMessagingService(client::messaging, this::mainThreadExecutor);
|
|
sessions = new OwnedBukkitSessionService(required("backendName"), secret,
|
|
() -> client.isRegistered() ? client.getEpoch() : 0L,
|
|
() -> client.isRegistered() ? client.getProxyEpoch() : new UUID(0L, 0L));
|
|
getServer().getServicesManager().register(BukkitMessagingService.class, service, this, ServicePriority.Normal);
|
|
getServer().getServicesManager().register(BukkitSessionService.class, sessions, this, ServicePriority.Normal);
|
|
getServer().getPluginManager().registerEvents(this, this);
|
|
getServer().getPluginManager().registerEvents(sessions, this);
|
|
getLogger().info("Shared MoonBridge messaging service started; registration runs asynchronously");
|
|
} catch (IllegalArgumentException invalid) {
|
|
// The configuration checks above and in BackendChannelClient name the key, never its value.
|
|
getLogger().severe("Cannot start MoonBridge messaging: " + invalid.getMessage());
|
|
getServer().getPluginManager().disablePlugin(this);
|
|
} catch (RuntimeException failure) {
|
|
// Do not include configuration values or authentication peer data.
|
|
getLogger().severe("Cannot start MoonBridge messaging: " + failure.getClass().getSimpleName());
|
|
getServer().getPluginManager().disablePlugin(this);
|
|
} finally {
|
|
if (secret != null) Arrays.fill(secret, (byte) 0);
|
|
}
|
|
}
|
|
|
|
private String required(String key) {
|
|
String value = getConfig().getString(key, "").trim();
|
|
if (value.isEmpty()) throw new IllegalArgumentException("Missing configuration key: " + key);
|
|
return value;
|
|
}
|
|
|
|
private Executor mainThreadExecutor(final Plugin owner) {
|
|
return command -> {
|
|
if (stopping || !owner.isEnabled()) throw new RejectedExecutionException("Message owner is disabled");
|
|
try {
|
|
getServer().getScheduler().runTask(owner, () -> {
|
|
if (!stopping && owner.isEnabled()) command.run();
|
|
});
|
|
} catch (RuntimeException rejected) {
|
|
// A disable can race the enabled check. Preserve the Executor
|
|
// rejection contract so the dispatcher releases its admission slot.
|
|
throw new RejectedExecutionException("Bukkit scheduler rejected the message owner", rejected);
|
|
}
|
|
};
|
|
}
|
|
|
|
@EventHandler public void onPluginDisable(PluginDisableEvent event) {
|
|
OwnedMessagingService active = service;
|
|
if (active != null) active.release(event.getPlugin());
|
|
}
|
|
|
|
@Override public void onDisable() {
|
|
stopping = true;
|
|
getServer().getServicesManager().unregisterAll(this);
|
|
if (sessions != null) { sessions.close(); sessions = null; }
|
|
if (service != null) { service.close(); service = null; }
|
|
if (client != null) { client.close(); client = null; }
|
|
}
|
|
}
|