implemented working fingerprinting prototype
This commit is contained in:
+218
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
+27
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
## Files originally from "TrackPack"
|
||||
https://github.com/ALaggyDev/TrackPack/blob/main/README.md
|
||||
|
||||
|
||||
Discovered by: [Laggy](https://github.com/ALaggyDev/) and [NikOverflow](https://github.com/NikOverflow)
|
||||
@@ -0,0 +1,33 @@
|
||||
import zipfile
|
||||
import hashlib
|
||||
import uuid
|
||||
import json
|
||||
|
||||
SERVER_URL = "http://localhost:8080/api/devicefingerprinting"
|
||||
packs = []
|
||||
|
||||
def file_sha1(path):
|
||||
h = hashlib.sha1()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
for i in range(0, 24):
|
||||
path = f"packs/{i}"
|
||||
|
||||
with zipfile.ZipFile(path, mode="w") as zf:
|
||||
zf.writestr(
|
||||
"pack.mcmeta",
|
||||
'{"pack":{"pack_format":22,"supported_formats":[22,1000],"description":"pack ' + str(i) + '"}}',
|
||||
)
|
||||
|
||||
hash = file_sha1(path)
|
||||
packs.append({
|
||||
"url": f"{SERVER_URL}/packs/{i}",
|
||||
"uuid": str(uuid.uuid4()),
|
||||
"hash": hash
|
||||
})
|
||||
|
||||
with open("packs.json", "w") as f:
|
||||
json.dump(packs, f, indent=4)
|
||||
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/0",
|
||||
"uuid": "b35f6e2f-1b50-4493-85be-fb18bd90f9bb",
|
||||
"hash": "7a39af839ea6484431f7b707759546bea991d435"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/1",
|
||||
"uuid": "71095b62-d5ef-4ab2-ba3b-3c1b403f5e34",
|
||||
"hash": "a9192ee73df1c5cff2c188fac6e9e638a1e7b6ce"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/2",
|
||||
"uuid": "a4dba0a2-f8f2-4a81-bbb2-a9a818820330",
|
||||
"hash": "6b85b0eb54865dae70bbda89746d83717dc2a214"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/3",
|
||||
"uuid": "79fa2dc4-8c84-45fc-a09f-d89906f0d900",
|
||||
"hash": "c7abf7a316f7e8c98985e8317a8b649e824e9f79"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/4",
|
||||
"uuid": "15702c9b-a22b-426d-b48a-3d65b0368e9a",
|
||||
"hash": "10cd0e2c46f192deb87ac75c149827d44a713017"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/5",
|
||||
"uuid": "3d702d41-8e2f-4920-8dd0-1fd2146da9fb",
|
||||
"hash": "8ad517d259e800b88a38ff00ee6721d5656822f2"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/6",
|
||||
"uuid": "c20a2e47-ef43-49da-a80d-adf238df3695",
|
||||
"hash": "798677405a4fd678892e1cf55585c8c91f82e1e2"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/7",
|
||||
"uuid": "7ce51b81-1263-4919-9f4e-bb749ffe6e2e",
|
||||
"hash": "af473b8eb7572f35d307bede5f2e20f263c0d804"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/8",
|
||||
"uuid": "0c70d586-fe48-4ffc-86b0-6b9ec3bfe045",
|
||||
"hash": "2fb698ff88f2436637641f3b2e6792201feb5144"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/9",
|
||||
"uuid": "c7af75a8-0b72-495d-a0ff-c1c40e229c13",
|
||||
"hash": "cf660460798eecf451d639873cc1fedc4661db1b"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/10",
|
||||
"uuid": "248dbce6-4b2a-44b5-b038-8d718b0ced99",
|
||||
"hash": "a8ebe708d0f3747c76e4e5e68db5dcb561922706"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/11",
|
||||
"uuid": "10979174-cb02-40eb-a754-275551ad608d",
|
||||
"hash": "54961b48db1582a1a0981c8cc9be5ae0f3122cf3"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/12",
|
||||
"uuid": "a361cfa7-674c-4493-a4cf-4baff851f276",
|
||||
"hash": "013719dc8da79c96b45a1c5319c20bffe1a56cc9"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/13",
|
||||
"uuid": "24b39bdb-ada9-40ec-9e3a-132c74b81dc6",
|
||||
"hash": "206898c6b6600d2648b2d79c61fc6255b19587d9"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/14",
|
||||
"uuid": "158fc5b4-be2c-4f7a-98cb-af5993adcc90",
|
||||
"hash": "061b266a7c526fb3a3152a4ea70ca5592e0b503c"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/15",
|
||||
"uuid": "4f9097a7-be02-48ad-919c-f292307f8490",
|
||||
"hash": "45a667a0fe06246defabca14ef1271fb6db5a1ac"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/16",
|
||||
"uuid": "3ce31e60-7e8a-4fb1-8c6d-da9065bea798",
|
||||
"hash": "75bb12e46203d49e89aa9a826d267552372758bc"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/17",
|
||||
"uuid": "cd978e5c-3de0-4ada-8ec5-3a88a305eec6",
|
||||
"hash": "5b20261f7be03e83e9c52307f1408b0c5e58358c"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/18",
|
||||
"uuid": "75001e58-3999-4779-a1d1-43ab161770ce",
|
||||
"hash": "544420cffb6c17113c06fb49eeba892c208719d3"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/19",
|
||||
"uuid": "6a7005a9-c2ca-476d-9a12-07d120ee121a",
|
||||
"hash": "fcc066a4d3193b60b102e3d906ad8dc0b0fcf65b"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/20",
|
||||
"uuid": "521c0d84-d82e-49ef-b096-d9b90f15aa19",
|
||||
"hash": "4545835983ec7f07d02675a69181a80dc396f038"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/21",
|
||||
"uuid": "c1b590c5-43fc-41e3-83c0-47f35b14f845",
|
||||
"hash": "8d4c670eaefc0482734e839b72758226dde13bc3"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/22",
|
||||
"uuid": "43958a18-c087-4f2b-a6ea-066231606eb1",
|
||||
"hash": "004282602f7bdbb7cd7724f23aae23876f224092"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080/api/devicefingerprinting/packs/23",
|
||||
"uuid": "4b91ac81-9de4-4c2b-a876-47e621496d10",
|
||||
"hash": "dae68eae109e08ea4c4c943905502eb331939f64"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
!base.zip
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,6 +7,7 @@ import org.bukkit.configuration.ConfigurationSection;
|
||||
import spark.Request;
|
||||
import spark.Spark;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -43,12 +44,16 @@ public class HttpServer {
|
||||
this.applianceName = appliance.getClass().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
public void rawGet(String path, BiFunction<Request, spark.Response, Object> onCall) {
|
||||
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req, resp)));
|
||||
}
|
||||
|
||||
public void get(String path, Function<Request, Object> onCall) {
|
||||
Spark.get(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||
}
|
||||
|
||||
public void rawPost(String path, Function<Request, Object> onCall) {
|
||||
Spark.post(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req)));
|
||||
public void rawPost(String path, BiFunction<Request, spark.Response, Object> onCall) {
|
||||
Spark.post(this.buildRoute(path), (req, resp) -> this.process(() -> onCall.apply(req, resp)));
|
||||
}
|
||||
|
||||
public <TRequest> void post(String path, Class<TRequest> clazz, RequestProvider<TRequest, Request, Object> onCall) {
|
||||
|
||||
Reference in New Issue
Block a user