implemented working fingerprinting prototype
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.deviceFingerprinting;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import eu.mhsl.craftattack.spawn.core.Main;
|
||||
import eu.mhsl.craftattack.spawn.core.api.server.HttpServer;
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
|
||||
import net.kyori.adventure.resource.ResourcePackInfo;
|
||||
import net.kyori.adventure.resource.ResourcePackRequest;
|
||||
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;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
public class DeviceFingerprinting extends Appliance {
|
||||
public record PackInfo(String url, UUID uuid, String hash) {
|
||||
private static final String failingUrl = "http://127.0.0.1:0";
|
||||
public PackInfo asFailing() {
|
||||
return new PackInfo(failingUrl, this.uuid, this.hash);
|
||||
}
|
||||
}
|
||||
|
||||
public enum PackStatus {
|
||||
ERROR,
|
||||
SUCCESS;
|
||||
|
||||
public static PackStatus fromBukkitStatus(PlayerResourcePackStatusEvent.Status status) {
|
||||
return switch(status) {
|
||||
case DISCARDED -> SUCCESS;
|
||||
case FAILED_DOWNLOAD -> ERROR;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + status);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum PlayerStatus {
|
||||
PREPARATION,
|
||||
TESTING,
|
||||
FINISHED;
|
||||
}
|
||||
|
||||
public record FingerprintData(
|
||||
Player player,
|
||||
PlayerStatus status,
|
||||
@Nullable Long fingerPrint,
|
||||
@Nullable List<PackStatus> pendingPacks
|
||||
) {
|
||||
public FingerprintData(Player player) {
|
||||
this(player, PlayerStatus.PREPARATION, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PackInfo> packs;
|
||||
private final Map<Player, List<PackStatus>> pendingPacks = new WeakHashMap<>();
|
||||
|
||||
@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 onPackReceive(Player player, UUID packId, PlayerResourcePackStatusEvent.Status status) {
|
||||
if(!this.pendingPacks.containsKey(player)) return;
|
||||
|
||||
PackInfo pack = this.packs.stream()
|
||||
.filter(packInfo -> packInfo.uuid.equals(packId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(pack == null) return;
|
||||
int packIndex = this.packs.indexOf(pack);
|
||||
|
||||
List<PackStatus> currentPendingStatus = this.pendingPacks.get(player);
|
||||
try {
|
||||
currentPendingStatus.set(packIndex, PackStatus.fromBukkitStatus(status));
|
||||
System.out.println(packIndex + " > " + PackStatus.fromBukkitStatus(status));
|
||||
} catch(IllegalStateException ignored) {
|
||||
return;
|
||||
}
|
||||
|
||||
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> 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) {
|
||||
for (int i = 0; i < this.packs.size(); i++) {
|
||||
if ((fingerprintId & (1L << i)) != 0) {
|
||||
PackInfo pack = this.packs.get(i);
|
||||
this.sendPack(player, pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPack(Player player, PackInfo pack) {
|
||||
System.out.println("Sending pack: " + pack.url);
|
||||
player.sendResourcePacks(
|
||||
ResourcePackRequest.resourcePackRequest()
|
||||
.required(true)
|
||||
.packs(ResourcePackInfo.resourcePackInfo(pack.uuid, URI.create(pack.url), pack.hash))
|
||||
);
|
||||
}
|
||||
|
||||
private List<DeviceFingerprinting.PackInfo> readPacksFromConfig() {
|
||||
try (InputStreamReader reader = new InputStreamReader(Objects.requireNonNull(Main.class.getResourceAsStream("/deviceFingerprinting/packs.json")))) {
|
||||
Type packListType = new TypeToken<List<DeviceFingerprinting.PackInfo>>(){}.getType();
|
||||
List<DeviceFingerprinting.PackInfo> packs = new Gson().fromJson(reader, packListType);
|
||||
|
||||
if (packs.isEmpty())
|
||||
throw new IllegalStateException("No resource packs found in packs.json.");
|
||||
|
||||
return packs;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to parse packs.json.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
|
||||
apiBuilder.rawGet(
|
||||
"base.zip",
|
||||
(request, response) -> this.servePack("base.zip", response)
|
||||
);
|
||||
|
||||
for(int i = 0; i < this.packs.size(); i++) {
|
||||
int packIndex = i;
|
||||
apiBuilder.rawGet(
|
||||
String.format("packs/%d", i),
|
||||
(request, response) -> this.servePack(String.valueOf(packIndex), response)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Object servePack(String name, Response response) {
|
||||
try {
|
||||
String resourcePath = String.format("/deviceFingerprinting/packs/%s", name);
|
||||
var inputStream = Main.class.getResourceAsStream(resourcePath);
|
||||
|
||||
if (inputStream == null) {
|
||||
throw new IllegalStateException("Pack file not found: " + resourcePath);
|
||||
}
|
||||
|
||||
response.header("Content-Type", "application/zip");
|
||||
response.header("Content-Disposition", String.format("attachment; filename=\"pack-%s.zip\"", name));
|
||||
|
||||
var outputStream = response.raw().getOutputStream();
|
||||
inputStream.transferTo(outputStream);
|
||||
outputStream.close();
|
||||
|
||||
return HttpServer.nothing;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(String.format("Failed to serve pack '%s'", name), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull List<Listener> listeners() {
|
||||
return List.of(
|
||||
new PlayerJoinListener()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package eu.mhsl.craftattack.spawn.common.appliances.tooling.deviceFingerprinting;
|
||||
|
||||
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceListener;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
|
||||
|
||||
class PlayerJoinListener extends ApplianceListener<DeviceFingerprinting> {
|
||||
@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());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onResourcePackEvent(PlayerResourcePackStatusEvent event) {
|
||||
this.getAppliance().onPackReceive(
|
||||
event.getPlayer(),
|
||||
event.getID(),
|
||||
event.getStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user