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); + }); + } +}