修复infohash计算逻辑
Change-Id: I2b82b42e457b9fcc1e6e86a6b1b34ded6d60ffb3
diff --git a/front/src/config.js b/front/src/config.js
index 0239e94..5d14657 100644
--- a/front/src/config.js
+++ b/front/src/config.js
@@ -1 +1 @@
-export const API_BASE_URL = "http://10.126.59.25:8082";
\ No newline at end of file
+export const API_BASE_URL = "http://192.168.5.230:8082";
diff --git a/src/main/java/tracker/EnhancedInfoHashCalculator.java b/src/main/java/tracker/EnhancedInfoHashCalculator.java
new file mode 100644
index 0000000..ef837bc
--- /dev/null
+++ b/src/main/java/tracker/EnhancedInfoHashCalculator.java
@@ -0,0 +1,185 @@
+package tracker;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.security.MessageDigest;
+import java.util.Map;
+import com.dampcake.bencode.Bencode;
+import com.dampcake.bencode.Type;
+
+/**
+ * Enhanced InfoHash calculator that matches qBittorrent's calculation
+ *
+ * The key issues with infoHash calculation are:
+ * 1. Bencode libraries may encode data differently
+ * 2. The original torrent's info dictionary bytes should be preserved
+ * 3. Re-encoding might change the binary representation
+ */
+public class EnhancedInfoHashCalculator {
+
+ /**
+ * Calculate infoHash by extracting the original info dictionary bytes
+ * from the torrent file, rather than re-encoding the parsed data
+ */
+ public static String calculateInfoHash(File torrentFile) throws Exception {
+ byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
+
+ // Find the info dictionary in the raw torrent data
+ // Look for the pattern "4:info" which indicates the start of the info dictionary
+ int infoStart = findInfoDictionary(torrentData);
+ if (infoStart == -1) {
+ throw new Exception("Could not find info dictionary in torrent file");
+ }
+
+ // Extract the info dictionary bytes directly from the original torrent
+ byte[] infoBytes = extractInfoBytes(torrentData, infoStart);
+
+ // Calculate SHA1 hash
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ byte[] digest = sha1.digest(infoBytes);
+
+ // Convert to hex string
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Find the position of "4:info" in the torrent data
+ */
+ private static int findInfoDictionary(byte[] data) {
+ byte[] pattern = "4:info".getBytes();
+
+ for (int i = 0; i <= data.length - pattern.length; i++) {
+ boolean found = true;
+ for (int j = 0; j < pattern.length; j++) {
+ if (data[i + j] != pattern[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Extract the info dictionary bytes from the original torrent data
+ */
+ private static byte[] extractInfoBytes(byte[] data, int infoStart) throws Exception {
+ // Skip "4:info" to get to the actual dictionary content
+ int dictStart = infoStart + 6; // "4:info".length()
+
+ if (dictStart >= data.length || data[dictStart] != 'd') {
+ throw new Exception("Invalid info dictionary format");
+ }
+
+ // Find the matching 'e' that closes the info dictionary
+ int dictEnd = findMatchingEnd(data, dictStart);
+ if (dictEnd == -1) {
+ throw new Exception("Could not find end of info dictionary");
+ }
+
+ // Extract the info dictionary bytes (including 'd' and 'e')
+ int length = dictEnd - dictStart + 1;
+ byte[] infoBytes = new byte[length];
+ System.arraycopy(data, dictStart, infoBytes, 0, length);
+
+ return infoBytes;
+ }
+
+ /**
+ * Find the matching 'e' for a dictionary that starts with 'd'
+ */
+ private static int findMatchingEnd(byte[] data, int start) {
+ if (start >= data.length || data[start] != 'd') {
+ return -1;
+ }
+
+ int depth = 0;
+ for (int i = start; i < data.length; i++) {
+ if (data[i] == 'd' || data[i] == 'l') {
+ depth++;
+ } else if (data[i] == 'e') {
+ depth--;
+ if (depth == 0) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * For comparison: calculate using the re-encoding method (your original approach)
+ */
+ public static String calculateInfoHashByReencoding(File torrentFile) throws Exception {
+ byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
+ Bencode bencode = new Bencode();
+
+ @SuppressWarnings("unchecked")
+ Map<String,Object> meta = bencode.decode(torrentData, Type.DICTIONARY);
+ @SuppressWarnings("unchecked")
+ Map<String,Object> info = (Map<String,Object>) meta.get("info");
+
+ if (info == null) {
+ throw new Exception("No info dictionary found");
+ }
+
+ // Re-encode the info dictionary
+ byte[] infoBytes = bencode.encode(info);
+
+ // Calculate SHA1
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ byte[] digest = sha1.digest(infoBytes);
+
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+
+ return sb.toString();
+ }
+
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.out.println("Usage: java EnhancedInfoHashCalculator <torrent-file>");
+ return;
+ }
+
+ File torrentFile = new File(args[0]);
+ if (!torrentFile.exists()) {
+ System.out.println("Torrent file not found: " + args[0]);
+ return;
+ }
+
+ try {
+ System.out.println("=== InfoHash Calculation Comparison ===");
+ System.out.println("File: " + torrentFile.getName());
+
+ String directHash = calculateInfoHash(torrentFile);
+ System.out.println("Direct extraction method: " + directHash);
+ System.out.println("Direct (uppercase): " + directHash.toUpperCase());
+
+ String reencodingHash = calculateInfoHashByReencoding(torrentFile);
+ System.out.println("Re-encoding method: " + reencodingHash);
+ System.out.println("Re-encoding (uppercase): " + reencodingHash.toUpperCase());
+
+ if (directHash.equals(reencodingHash)) {
+ System.out.println("✓ Both methods produce the same result");
+ } else {
+ System.out.println("✗ Methods produce different results!");
+ System.out.println("This suggests the Bencode re-encoding is changing the data");
+ }
+
+ } catch (Exception e) {
+ System.out.println("Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/tracker/Tracker.java b/src/main/java/tracker/Tracker.java
index 1b38806..11e0265 100644
--- a/src/main/java/tracker/Tracker.java
+++ b/src/main/java/tracker/Tracker.java
@@ -291,25 +291,20 @@
Path target = storageDir.resolve(seedid + "_" + filename);
Files.copy(TTorent.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
- // attempt to parse infoHash, but don't fail if parsing fails
+ // Calculate infoHash using ISO_8859_1 encoding method to match qBittorrent
String infoHash = null;
try {
- byte[] torrentData = Files.readAllBytes(target);
- Bencode bencode = new Bencode();
- @SuppressWarnings("unchecked")
- Map<String,Object> meta = bencode.decode(torrentData, Type.DICTIONARY);
- @SuppressWarnings("unchecked")
- Map<String,Object> info = (Map<String,Object>) meta.get("info");
- byte[] infoBytes = bencode.encode(info);
- MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
- byte[] digest = sha1.digest(infoBytes);
- StringBuilder sb = new StringBuilder();
- for (byte b1 : digest) {
- sb.append(String.format("%02x", b1));
- }
- infoHash = sb.toString();
+ infoHash = calculateInfoHashReencoding(target.toFile());
+ System.out.println("InfoHash (ISO_8859_1): " + infoHash);
} catch (Exception e) {
System.err.println("Warning: could not parse torrent infoHash: " + e.getMessage());
+ // Fallback to direct extraction method
+ try {
+ infoHash = calculateInfoHashDirect(target.toFile());
+ System.out.println("InfoHash (Direct): " + infoHash);
+ } catch (Exception e2) {
+ System.err.println("Warning: fallback infoHash calculation also failed: " + e2.getMessage());
+ }
}
EntityManager em = emf.createEntityManager();
@@ -387,4 +382,178 @@
em.close();
}
}
+
+ /**
+ * Calculate infoHash by extracting the original info dictionary bytes
+ * from the torrent file, rather than re-encoding the parsed data.
+ * This method preserves the original binary representation.
+ */
+ private String calculateInfoHashDirect(File torrentFile) throws Exception {
+ byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
+
+ // Find the info dictionary in the raw torrent data
+ int infoStart = findInfoDictionary(torrentData);
+ if (infoStart == -1) {
+ throw new Exception("Could not find info dictionary in torrent file");
+ }
+
+ // Extract the info dictionary bytes directly from the original torrent
+ byte[] infoBytes = extractInfoBytes(torrentData, infoStart);
+
+ // Debug: print first few bytes of info dict
+ System.out.print("Info dict starts with: ");
+ for (int i = 0; i < Math.min(20, infoBytes.length); i++) {
+ System.out.printf("%02x ", infoBytes[i] & 0xff);
+ }
+ System.out.println();
+
+ // Calculate SHA1 hash
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ byte[] digest = sha1.digest(infoBytes);
+
+ // Convert to hex string
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Correct method using ISO_8859_1 encoding for infohash calculation
+ * This matches qBittorrent's calculation method
+ */
+ private String calculateInfoHashReencoding(File torrentFile) throws Exception {
+ byte[] torrentData = Files.readAllBytes(torrentFile.toPath());
+
+ // Use ISO_8859_1 charset for infohash calculation (as per BitTorrent specification)
+ Bencode bencodeInfoHash = new Bencode(java.nio.charset.StandardCharsets.ISO_8859_1);
+
+ @SuppressWarnings("unchecked")
+ Map<String,Object> meta = bencodeInfoHash.decode(torrentData, Type.DICTIONARY);
+ @SuppressWarnings("unchecked")
+ Map<String,Object> info = (Map<String,Object>) meta.get("info");
+
+ if (info == null) {
+ throw new Exception("No info dictionary found");
+ }
+
+ // Re-encode the info dictionary using ISO_8859_1
+ byte[] infoBytes = bencodeInfoHash.encode(info);
+
+ // Calculate SHA1 hash
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ byte[] digest = sha1.digest(infoBytes);
+
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xff));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Find the position of "4:info" in the torrent data
+ */
+ private int findInfoDictionary(byte[] data) {
+ byte[] pattern = "4:info".getBytes();
+
+ for (int i = 0; i <= data.length - pattern.length; i++) {
+ boolean found = true;
+ for (int j = 0; j < pattern.length; j++) {
+ if (data[i + j] != pattern[j]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Extract the info dictionary bytes from the original torrent data
+ */
+ private byte[] extractInfoBytes(byte[] data, int infoStart) throws Exception {
+ // Skip "4:info" to get to the actual dictionary content
+ int dictStart = infoStart + 6; // "4:info".length()
+
+ if (dictStart >= data.length || data[dictStart] != 'd') {
+ throw new Exception("Invalid info dictionary format");
+ }
+
+ // Find the matching 'e' that closes the info dictionary
+ int dictEnd = findMatchingEnd(data, dictStart);
+ if (dictEnd == -1) {
+ throw new Exception("Could not find end of info dictionary");
+ }
+
+ // Extract the info dictionary bytes (including 'd' and 'e')
+ int length = dictEnd - dictStart + 1;
+ byte[] infoBytes = new byte[length];
+ System.arraycopy(data, dictStart, infoBytes, 0, length);
+
+ return infoBytes;
+ }
+
+ /**
+ * Find the matching 'e' for a dictionary that starts with 'd'
+ */
+ private int findMatchingEnd(byte[] data, int start) {
+ if (start >= data.length || data[start] != 'd') {
+ return -1;
+ }
+
+ int depth = 0;
+ int i = start;
+
+ while (i < data.length) {
+ byte b = data[i];
+
+ if (b == 'd' || b == 'l') {
+ // Dictionary or list start
+ depth++;
+ i++;
+ } else if (b == 'e') {
+ // Dictionary or list end
+ depth--;
+ if (depth == 0) {
+ return i;
+ }
+ i++;
+ } else if (b == 'i') {
+ // Integer: i<number>e
+ i++; // skip 'i'
+ while (i < data.length && data[i] != 'e') {
+ i++;
+ }
+ if (i < data.length) i++; // skip 'e'
+ } else if (b >= '0' && b <= '9') {
+ // String: <length>:<string>
+ int lengthStart = i;
+ while (i < data.length && data[i] >= '0' && data[i] <= '9') {
+ i++;
+ }
+ if (i < data.length && data[i] == ':') {
+ // Parse length
+ String lengthStr = new String(data, lengthStart, i - lengthStart);
+ int length = Integer.parseInt(lengthStr);
+ i++; // skip ':'
+ i += length; // skip string content
+ } else {
+ // Invalid format
+ return -1;
+ }
+ } else {
+ // Unknown character
+ i++;
+ }
+ }
+
+ return -1;
+ }
}
\ No newline at end of file