修复infohash计算逻辑

Change-Id: I2b82b42e457b9fcc1e6e86a6b1b34ded6d60ffb3
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();
+        }
+    }
+}