Initial commit for DataLock

This commit is contained in:
2022-05-09 02:58:27 +02:00
commit 7274eee497
11 changed files with 772 additions and 0 deletions
@@ -0,0 +1,47 @@
package com.alttd.datalock;
import com.google.inject.Inject;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import org.slf4j.Logger;
import java.nio.file.Path;
@Plugin(
id = "data-lock",
name = "DataLock",
version = BuildConstants.VERSION,
description = "A proxy plugin that can be utilized to prevent any plugins from editing data that is currently in use elsewhere",
url = "https://alttd.com",
authors = {"Teriuihi"}
)
public class DataLock {
private static DataLock instance;
private final ProxyServer server;
private final Logger logger;
private final Path dataDirectory;
@Inject
public DataLock(ProxyServer proxyServer, Logger proxyLogger, @DataDirectory Path proxydataDirectory) {
instance = this;
server = proxyServer;
logger = proxyLogger;
dataDirectory = proxydataDirectory;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent event) {
}
public static DataLock getInstance() {
return instance;
}
public static Logger getLogger() {
return getInstance().logger;
}
}
@@ -0,0 +1,144 @@
package com.alttd.datalock;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import java.util.*;
public class EventListener {
private final HashMap<ChannelIdentifier, HashSet<Lock>> channelLockMap = new HashMap<>();
private final static List<ChannelIdentifier> channelIdentifierList = new ArrayList<>();
public EventListener(List<ChannelIdentifier> channelIdentifierList)
{
EventListener.reload(channelIdentifierList);
}
public static void reload(List<ChannelIdentifier> channelIdentifierList)
{
EventListener.channelIdentifierList.clear();
EventListener.channelIdentifierList.addAll(channelIdentifierList);
}
@Subscribe
public void onPluginMessageEvent(PluginMessageEvent event) {
ChannelIdentifier identifier = event.getIdentifier();
if (!EventListener.channelIdentifierList.contains(identifier))
return;
event.setResult(PluginMessageEvent.ForwardResult.handled());
if(event.getSource() instanceof Player) {
Logger.warn("Received plugin message from a player");
return;
}
if (!(event.getSource() instanceof ServerConnection serverConnection)) {
Logger.warn("Received plugin message from something other than a server.");
return;
}
HashSet<Lock> hashLock = channelLockMap.getOrDefault(identifier, new HashSet<>());
ByteArrayDataInput in = ByteStreams.newDataInput(event.getData());
String channel;
try {
channel = in.readUTF();
} catch (IllegalStateException e) {
Logger.error("Input stream did not contain enough data, please contact %'s developer.",
identifier.getId());
return;
}
String data;
try {
data = in.readUTF();
} catch (IllegalStateException e) {
Logger.error("Input stream did not contain enough data, please contact %'s developer.",
identifier.getId());
return;
}
switch (channel.toLowerCase()) {
case "try-lock" -> tryLock(identifier, hashLock, data, serverConnection);
case "check-lock" -> checkLock(identifier, hashLock, data, serverConnection);
case "try-unlock" -> tryUnlock(identifier, hashLock, data, serverConnection);
}
}
private void tryLock(ChannelIdentifier identifier, HashSet<Lock> lockSet, String data, ServerConnection serverConnection) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("try-lock-result");
Lock lock = new Lock(serverConnection.getServerInfo().hashCode(), data);
if (lockSet.contains(lock)) //An entry from this server already exists, so we can say that it's locked
{
out.writeBoolean(true);
serverConnection.sendPluginMessage(identifier, out.toByteArray());
return;
}
Optional<Lock> first = lockSet.stream().filter(a -> a.compareTo(lock) == 0).findFirst();
if (first.isPresent()) //An entry from another server exists, so we can't lock it
{
out.writeBoolean(false);
serverConnection.sendPluginMessage(identifier, out.toByteArray());
return;
}
//Lock the data
lockSet.add(lock);
channelLockMap.put(identifier, lockSet);
out.writeBoolean(true);
serverConnection.sendPluginMessage(identifier, out.toByteArray());
}
private void checkLock(ChannelIdentifier identifier, HashSet<Lock> lockSet, String data, ServerConnection serverConnection) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
Lock lock = new Lock(serverConnection.hashCode(), data);
out.writeUTF("check-lock-result");
if (lockSet.contains(lock)) //We locked this, but we still return true since it's locked
out.writeBoolean(true);
else if (lockSet.stream().anyMatch(a -> a.compareTo(lock) == 0))
out.writeBoolean(true); //There is a lock (not ours, but it's still locked)
else
out.writeBoolean(false); //The data is not locked
serverConnection.sendPluginMessage(identifier, out.toByteArray());
}
private void tryUnlock(ChannelIdentifier identifier, HashSet<Lock> lockSet, String data, ServerConnection serverConnection) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("try-unlock-result");
Lock lock = new Lock(serverConnection.getServerInfo().hashCode(), data);
if (lockSet.contains(lock)) //Lock is in the list, but it's made by this server, so we can unlock it
{
out.writeBoolean(true);
lockSet.remove(lock);
channelLockMap.put(identifier, lockSet);
serverConnection.sendPluginMessage(identifier, out.toByteArray());
return;
}
Optional<Lock> first = lockSet.stream().filter(a -> a.compareTo(lock) == 0).findFirst();
if (first.isEmpty()) //There is no entry with this data, so we can say it's unlocked
{
out.writeBoolean(true);
serverConnection.sendPluginMessage(identifier, out.toByteArray());
return;
}
//There is an entry with this data, but it's not owned by this server, so we can't unlock it
out.writeBoolean(false);
serverConnection.sendPluginMessage(identifier, out.toByteArray());
}
}
@@ -0,0 +1,40 @@
package com.alttd.datalock;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class Lock implements Comparable {
private final int serverHash;
private final String data;
public Lock(int serverHash, String data) {
this.serverHash = serverHash;
this.data = data;
}
@Override
public final boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Lock other = (Lock) o;
return data.equals(other.data) && serverHash == other.serverHash;
}
@Override
public final int hashCode() {
return Objects.hash(serverHash, data);
}
@Override
public int compareTo(@NotNull Object o) {
return ((Lock) o).data.compareTo(data);
}
}
@@ -0,0 +1,34 @@
package com.alttd.datalock;
public class Logger {
static private final org.slf4j.Logger logger;
static {
logger = DataLock.getLogger();
}
public static void info(String info, String... variables)
{
for (String variable : variables) {
info = info.replaceFirst("%", variable);
}
logger.info(info);
}
public static void warn(String warning, String... variables)
{
for (String variable : variables) {
warning = warning.replaceFirst("%", variable);
}
logger.warn(warning);
}
public static void error(String severe, String... variables)
{
for (String variable : variables) {
severe = severe.replaceFirst("%", variable);
}
logger.error(severe);
}
}
@@ -0,0 +1,7 @@
package com.alttd.datalock;
// The constants are replaced before compilation
public class BuildConstants {
public static final String VERSION = "${version}";
}