blob: ef837bc7783085f7100b464003cbb6ae873bc358 [file] [log] [blame]
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();
}
}
}