From 670b11b49ab7274a1befb54b732a3132b08cde6e Mon Sep 17 00:00:00 2001
From: mykola2312 <49044616+mykola2312@users.noreply.github.com>
Date: Sun, 20 Oct 2024 05:34:32 +0300
Subject: [PATCH] implement InfoHash torrent data type
---
notes.txt | 3 +
pom.xml | 5 ++
.../java/com/mykola2312/retracker/App.java | 2 +-
.../retracker/tracker/InfoHash.java | 70 +++++++++++++++++++
.../retracker/tracker/InfoHashTest.java | 67 ++++++++++++++++++
5 files changed, 146 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/com/mykola2312/retracker/tracker/InfoHash.java
create mode 100644 src/test/java/com/mykola2312/retracker/tracker/InfoHashTest.java
diff --git a/notes.txt b/notes.txt
index ec7c64f..7b1b632 100644
--- a/notes.txt
+++ b/notes.txt
@@ -17,5 +17,8 @@ When there is need to expose local tracker a tracker endpoint should be declare
is HTTP or UDP server responsing to BitTorrent announces just like if it was any other regular torrent tracker.
+Announce request coming from local peer contains local (peer) data: ID, uploaded, downloaded, and so on.
+Announce response contains peer set.
+
Maven exec with args:
mvn exec:java -Dexec.args=''
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 2e5c608..b567876 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,6 +70,11 @@
jackson-databind
2.18.0
+
+ commons-codec
+ commons-codec
+ 1.17.1
+
diff --git a/src/main/java/com/mykola2312/retracker/App.java b/src/main/java/com/mykola2312/retracker/App.java
index 5d9e07d..69c5ac1 100644
--- a/src/main/java/com/mykola2312/retracker/App.java
+++ b/src/main/java/com/mykola2312/retracker/App.java
@@ -24,8 +24,8 @@ public class App {
private static final Logger log = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
- final Option logDirOpt = new Option("l", "log", true, "log dir path");
final Option configFileOpt = new Option("c", "config", true, "config.json path");
+ final Option logDirOpt = new Option("l", "log", true, "log dir path");
final Options options = new Options()
.addOption(configFileOpt)
.addOption(logDirOpt);
diff --git a/src/main/java/com/mykola2312/retracker/tracker/InfoHash.java b/src/main/java/com/mykola2312/retracker/tracker/InfoHash.java
new file mode 100644
index 0000000..3fa76fc
--- /dev/null
+++ b/src/main/java/com/mykola2312/retracker/tracker/InfoHash.java
@@ -0,0 +1,70 @@
+package com.mykola2312.retracker.tracker;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.BitSet;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.URLCodec;
+
+public class InfoHash {
+ public static final int HASH_LENGTH = 20;
+
+ private byte[] hash = null;
+
+ public InfoHash() {
+ this.hash = null;
+ }
+
+ public InfoHash(byte[] hash) {
+ this.hash = hash;
+ }
+
+ public byte[] bytes() {
+ return this.hash;
+ }
+
+ public void fromURLEncoded(String urlParam) throws DecoderException {
+ this.hash = URLCodec.decodeUrl(urlParam.getBytes());
+ }
+
+ public String toURLEncoded() {
+ return new String(URLCodec.encodeUrl(new BitSet(), this.hash), StandardCharsets.US_ASCII);
+ }
+
+ public void fromString(String hashString) throws Exception {
+ if (hashString.length() != InfoHash.HASH_LENGTH * 2) {
+ throw new Exception(String.format("hash is not 40 characters length: %s, %d", hashString, hashString.length()));
+ }
+
+ char[] bytePair = new char[2];
+ hash = new byte[InfoHash.HASH_LENGTH];
+ for (int i = 0; i < InfoHash.HASH_LENGTH; i++) {
+ hashString.getChars(i*2, i*2 + 2, bytePair, 0);
+ hash[i] = (byte)Short.parseShort(new String(bytePair), 16);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < InfoHash.HASH_LENGTH; i++) {
+ out.append(String.format("%02x", hash[i]));
+ }
+
+ return out.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof InfoHash)) return false;
+ InfoHash other = (InfoHash)o;
+ return Arrays.equals(this.hash, other.hash);
+ }
+
+ @Override
+ public final int hashCode() {
+ return this.hash != null ? Arrays.hashCode(this.hash) : 0;
+ }
+}
diff --git a/src/test/java/com/mykola2312/retracker/tracker/InfoHashTest.java b/src/test/java/com/mykola2312/retracker/tracker/InfoHashTest.java
new file mode 100644
index 0000000..d049ed4
--- /dev/null
+++ b/src/test/java/com/mykola2312/retracker/tracker/InfoHashTest.java
@@ -0,0 +1,67 @@
+package com.mykola2312.retracker.tracker;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class InfoHashTest {
+ @Test
+ public void testHashString() {
+ final String hashString = "360775c6629eb06e60d90201aed1b7bc49a1ce16";
+
+ assertDoesNotThrow(() -> {
+ InfoHash hash = new InfoHash();
+
+ hash.fromString(hashString);
+ assertEquals(hashString, hash.toString());
+ });
+ }
+
+ @Test
+ public void testHashEquals() {
+ final String hashString = "360775c6629eb06e60d90201aed1b7bc49a1ce16";
+
+ assertDoesNotThrow(() -> {
+ InfoHash hashOne = new InfoHash();
+ InfoHash hashTwo = new InfoHash();
+
+ hashOne.fromString(hashString);
+ hashTwo.fromString(hashString);
+
+ assertEquals(true, hashOne.equals(hashTwo));
+ });
+ }
+
+ @Test
+ public void testHashCode() {
+ final String hashString = "360775c6629eb06e60d90201aed1b7bc49a1ce16";
+
+ assertDoesNotThrow(() -> {
+ InfoHash hashOne = new InfoHash();
+ InfoHash hashTwo = new InfoHash();
+
+ hashOne.fromString(hashString);
+ hashTwo.fromString(hashString);
+
+ assertEquals(true, hashOne.hashCode() == hashTwo.hashCode());
+ });
+ }
+
+ @Test
+ public void testURLEncode() {
+ final String hashString = "360775c6629eb06e60d90201aed1b7bc49a1ce16";
+
+ assertDoesNotThrow(() -> {
+ InfoHash first = new InfoHash();
+ first.fromString(hashString);
+
+ InfoHash second = new InfoHash();
+ second.fromURLEncoded(first.toURLEncoded());
+
+ System.out.printf("url encoded hash: %s\n", first.toURLEncoded());
+
+ assertEquals(first, second);
+ });
+ }
+}