diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/DeviceFingerprinting.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/DeviceFingerprinting.java index 73d1fde..92147c3 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/DeviceFingerprinting.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/DeviceFingerprinting.java @@ -11,7 +11,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerResourcePackStatusEvent; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import spark.Response; import java.io.IOException; @@ -21,7 +20,7 @@ import java.net.URI; import java.util.*; public class DeviceFingerprinting extends Appliance { - public record PackInfo(String url, UUID uuid, String hash) { + public record PackInfo(@NotNull String url, @NotNull UUID uuid, @NotNull String hash) { private static final String failingUrl = "http://127.0.0.1:0"; public PackInfo asFailing() { return new PackInfo(failingUrl, this.uuid, this.hash); @@ -29,14 +28,15 @@ public class DeviceFingerprinting extends Appliance { } public enum PackStatus { - ERROR, - SUCCESS; + UNCACHED, + CACHED, + INVALID; public static PackStatus fromBukkitStatus(PlayerResourcePackStatusEvent.Status status) { return switch(status) { - case DISCARDED -> SUCCESS; - case FAILED_DOWNLOAD -> ERROR; - default -> throw new IllegalStateException("Unexpected value: " + status); + case DISCARDED -> CACHED; + case FAILED_DOWNLOAD -> UNCACHED; + default -> INVALID; }; } } @@ -44,102 +44,60 @@ public class DeviceFingerprinting extends Appliance { public enum PlayerStatus { PREPARATION, TESTING, - FINISHED; - } - - public record FingerprintData( - Player player, - PlayerStatus status, - @Nullable Long fingerPrint, - @Nullable List pendingPacks - ) { - public FingerprintData(Player player) { - this(player, PlayerStatus.PREPARATION, null, null); - } + FINISHED, + NEW } private List packs; - private final Map> pendingPacks = new WeakHashMap<>(); + private final Map fingerprints = new WeakHashMap<>(); + private final UUID basePackId = UUID.randomUUID(); @Override public void onEnable() { this.packs = this.readPacksFromConfig(); - } - public void testAllPacks(Player player) { - this.pendingPacks.put(player, Arrays.asList(new PackStatus[this.packs.size()])); - System.out.println("Sending packs..."); - this.packs.forEach(pack -> this.sendPack(player, pack.asFailing())); + public void startFingerprinting(Player player) { + this.fingerprints.put(player, FingerprintData.create(player)); + Main.logger().info(String.format("Sending base ressource-pack with id '%s' to '%s'%n", this.basePackId, player.getName())); + this.sendPack(player, new PackInfo("http://localhost:8080/api/devicefingerprinting/base.zip", this.basePackId, "3296e8bdd30b4f7cffd11c780a1dc70da2948e71")); } - public void onPackReceive(Player player, UUID packId, PlayerResourcePackStatusEvent.Status status) { - if(!this.pendingPacks.containsKey(player)) return; + public void onPackUpdate(Player player, UUID packId, PlayerResourcePackStatusEvent.Status status) { + if(!this.fingerprints.containsKey(player)) return; + FingerprintData playerFingerprint = this.fingerprints.get(player); + if(!playerFingerprint.isInTestingOrPreparation()) return; + + if(packId.equals(this.basePackId)) { + Main.logger().info(String.format("Base pack for '%s' updated: '%s'", player.getName(), status)); + + if(status != PlayerResourcePackStatusEvent.Status.ACCEPTED) return; + Main.logger().info(String.format("Base pack loaded successfully, sending now all packs to '%s'...", player.getName())); + playerFingerprint.setTesting(); + this.packs.forEach(pack -> this.sendPack(player, pack.asFailing())); + return; + } PackInfo pack = this.packs.stream() - .filter(packInfo -> packInfo.uuid.equals(packId)) - .findFirst() + .filter(packInfo -> Objects.equals(packInfo.uuid, packId)) + .findAny() .orElse(null); if(pack == null) return; int packIndex = this.packs.indexOf(pack); - List currentPendingStatus = this.pendingPacks.get(player); - try { - currentPendingStatus.set(packIndex, PackStatus.fromBukkitStatus(status)); - System.out.println(packIndex + " > " + PackStatus.fromBukkitStatus(status)); - } catch(IllegalStateException ignored) { - return; + List pendingPacks = playerFingerprint.getPendingPacks(); + PackStatus newPackStatus = PackStatus.fromBukkitStatus(status); + if(newPackStatus == PackStatus.INVALID) return; + pendingPacks.set(packIndex, newPackStatus); + + playerFingerprint.updateFingerprint(); + if(Objects.requireNonNull(playerFingerprint.getStatus()) == PlayerStatus.NEW) { + Main.logger().info(String.format("Sending fingerprint packs to Player '%s', as it is a unseen Player!", player.getName())); + this.sendNewFingerprint(player, Objects.requireNonNull(playerFingerprint.getFingerPrint())); } - - long fingerPrint = this.calculateFingerprintId(currentPendingStatus); - if(fingerPrint == -1) return; - - if(fingerPrint == 0) { - // new Player - long newFingerprintId = this.generateNewFingerprintId(); - System.out.println("New Fingerprint: " + newFingerprintId); - this.sendMarkedPacks(player, newFingerprintId); - } else { - System.out.println("Fingerprint: " + fingerPrint); - } - - this.pendingPacks.remove(player); } - private long calculateFingerprintId(List packStatus) { - long fingerprintId = 0; - for (int i = 0; i < packStatus.size(); i++) { - var status = packStatus.get(i); - if(status == null) return -1; - switch (status) { - case SUCCESS: - fingerprintId |= 1L << i; - break; - case ERROR: - break; - default: - return -1; - } - } - return fingerprintId; - } - - private long generateNewFingerprintId() { - long id = 0; - Random random = new Random(); - for (int i = 0; i < this.packs.size() / 2; i++) { - while (true) { - int bitIndex = random.nextInt(this.packs.size()); - if ((id & (1L << bitIndex)) == 0) { - id |= 1L << bitIndex; - break; - } - } - } - return id; - } - - private void sendMarkedPacks(Player player, long fingerprintId) { + private void sendNewFingerprint(Player player, long fingerprintId) { for (int i = 0; i < this.packs.size(); i++) { if ((fingerprintId & (1L << i)) != 0) { PackInfo pack = this.packs.get(i); @@ -149,7 +107,6 @@ public class DeviceFingerprinting extends Appliance { } public void sendPack(Player player, PackInfo pack) { - System.out.println("Sending pack: " + pack.url); player.sendResourcePacks( ResourcePackRequest.resourcePackRequest() .required(true) @@ -161,12 +118,9 @@ public class DeviceFingerprinting extends Appliance { try (InputStreamReader reader = new InputStreamReader(Objects.requireNonNull(Main.class.getResourceAsStream("/deviceFingerprinting/packs.json")))) { Type packListType = new TypeToken>(){}.getType(); List packs = new Gson().fromJson(reader, packListType); - - if (packs.isEmpty()) - throw new IllegalStateException("No resource packs found in packs.json."); - + if (packs.isEmpty()) throw new IllegalStateException("No resource packs found in packs.json."); return packs; - } catch (Exception e) { + } catch (IOException e) { throw new IllegalStateException("Failed to parse packs.json.", e); } } @@ -209,6 +163,10 @@ public class DeviceFingerprinting extends Appliance { } } + public List getPacks() { + return this.packs; + } + @Override protected @NotNull List listeners() { return List.of( diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/FingerprintData.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/FingerprintData.java new file mode 100644 index 0000000..f349e44 --- /dev/null +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/FingerprintData.java @@ -0,0 +1,91 @@ +package eu.mhsl.craftattack.spawn.common.appliances.tooling.deviceFingerprinting; + +import eu.mhsl.craftattack.spawn.core.Main; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +class FingerprintData { + public final Player player; + private DeviceFingerprinting.PlayerStatus status; + private @Nullable Long fingerPrint; + private final List pendingPacks; + int packCount = Main.instance().getAppliance(DeviceFingerprinting.class).getPacks().size(); + + private FingerprintData(Player player) { + this.player = player; + this.status = DeviceFingerprinting.PlayerStatus.PREPARATION; + this.fingerPrint = null; + this.pendingPacks = Arrays.asList(new DeviceFingerprinting.PackStatus[this.packCount]); + } + + public static FingerprintData create(Player player) { + return new FingerprintData(player); + } + + public void setTesting() { + this.status = DeviceFingerprinting.PlayerStatus.TESTING; + } + + public void updateFingerprint() { + long fingerPrint = 0; + for (int i = 0; i < this.pendingPacks.size(); i++) { + var status = this.pendingPacks.get(i); + if(status == null) return; + switch (status) { + case CACHED: + fingerPrint |= 1L << i; + break; + case UNCACHED: + break; + default: + return; + } + } + + if(fingerPrint == 0) { + this.status = DeviceFingerprinting.PlayerStatus.NEW; + this.fingerPrint = this.createNewFingerprint(); + Main.logger().info(String.format("Player %s's was marked as a new Player!", this.player.getName())); + } else { + this.status = DeviceFingerprinting.PlayerStatus.FINISHED; + this.fingerPrint = fingerPrint; + } + + Main.logger().info(String.format("Player %s's fingerprint is '%s'", this.player.getName(), fingerPrint)); + } + + private long createNewFingerprint() { + long id = 0; + Random random = new Random(); + for (int i = 0; i < this.packCount / 2; i++) { + while (true) { + int bitIndex = random.nextInt(this.packCount); + if ((id & (1L << bitIndex)) == 0) { + id |= 1L << bitIndex; + break; + } + } + } + return id; + } + + public List getPendingPacks() { + return this.pendingPacks; + } + + public DeviceFingerprinting.PlayerStatus getStatus() { + return this.status; + } + + public @Nullable Long getFingerPrint() { + return this.fingerPrint; + } + + public boolean isInTestingOrPreparation() { + return this.status == DeviceFingerprinting.PlayerStatus.TESTING || this.status == DeviceFingerprinting.PlayerStatus.PREPARATION; + } +} diff --git a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/PlayerJoinListener.java b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/PlayerJoinListener.java index 2d92e04..fec1983 100644 --- a/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/PlayerJoinListener.java +++ b/common/src/main/java/eu/mhsl/craftattack/spawn/common/appliances/tooling/deviceFingerprinting/PlayerJoinListener.java @@ -8,17 +8,12 @@ import org.bukkit.event.player.PlayerResourcePackStatusEvent; class PlayerJoinListener extends ApplianceListener { @EventHandler public void onJoin(PlayerJoinEvent event) { -// if(Bukkit.getServer().getServerResourcePack() != null && !event.getPlayer().hasResourcePack()) { -// System.out.println("NO RESSOURCEPACK"); -// return; -// } - - this.getAppliance().testAllPacks(event.getPlayer()); + this.getAppliance().startFingerprinting(event.getPlayer()); } @EventHandler public void onResourcePackEvent(PlayerResourcePackStatusEvent event) { - this.getAppliance().onPackReceive( + this.getAppliance().onPackUpdate( event.getPlayer(), event.getID(), event.getStatus()