From d40e1cbe4ee62d26500e149532e4fdb2c2fb9848 Mon Sep 17 00:00:00 2001 From: mykola2312 <49044616+mykola2312@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:25:47 +0300 Subject: [PATCH] begin working on bencode decoding --- .../mykola2312/retracker/bencode/BTree.java | 71 +++++++++++++++++++ .../retracker/bencode/error/BDecodeError.java | 20 ++++++ .../bencode/error/BDecodeParseError.java | 10 +++ .../bencode/error/BDecodeUnknown.java | 12 ++++ .../retracker/bencode/BTreeTest.java | 27 +++++++ 5 files changed, 140 insertions(+) create mode 100644 src/main/java/com/mykola2312/retracker/bencode/BTree.java create mode 100644 src/main/java/com/mykola2312/retracker/bencode/error/BDecodeError.java create mode 100644 src/main/java/com/mykola2312/retracker/bencode/error/BDecodeParseError.java create mode 100644 src/main/java/com/mykola2312/retracker/bencode/error/BDecodeUnknown.java create mode 100644 src/test/java/com/mykola2312/retracker/bencode/BTreeTest.java diff --git a/src/main/java/com/mykola2312/retracker/bencode/BTree.java b/src/main/java/com/mykola2312/retracker/bencode/BTree.java new file mode 100644 index 0000000..18de2e6 --- /dev/null +++ b/src/main/java/com/mykola2312/retracker/bencode/BTree.java @@ -0,0 +1,71 @@ +package com.mykola2312.retracker.bencode; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import com.mykola2312.retracker.bencode.error.BDecodeError; +import com.mykola2312.retracker.bencode.error.BDecodeParseError; +import com.mykola2312.retracker.bencode.error.BDecodeUnknown; + +public class BTree { + private BValue root = null; + + public BValue getRoot() { + return root; + } + + class BDecoder { + private byte[] data; + private int offset = 0; + + public BDecoder(byte[] data, int offset) { + this.data = data; + this.offset = offset; + } + + private Long parseLong(byte[] bytes) throws BDecodeParseError { + String strInt = new String(bytes, StandardCharsets.UTF_8); + try { + return Long.parseLong(strInt); + } catch (NumberFormatException e) { + throw new BDecodeParseError(data, offset, e); + } + } + + private static final byte BE_INTEGER = (byte)'i'; + private static final byte BE_LIST = (byte)'l'; + private static final byte BE_DICT = (byte)'d'; + private static final byte BE_STRING_SEP = (byte)':'; + private static final byte BE_END = (byte)'e'; + + public BValue decode() throws BDecodeError { + BType type; + + // consume and determine type + switch (data[offset]) { + case BE_INTEGER: type = BType.INTEGER; break; + case BE_LIST: type = BType.LIST; break; + case BE_DICT: type = BType.DICT; break; + default: throw new BDecodeUnknown(data, offset, data[offset]); + } + offset++; + + if (type.equals(BType.INTEGER)) { + // advance until we hit end marker + int end = offset; + while (data[end] != BDecoder.BE_END) { + end++; + } + // convert bytes to string and string to integer + byte[] bytes = Arrays.copyOfRange(data, offset, end); + return new BInteger(parseLong(bytes)); + } + + return null; + } + } + + public void decode(byte[] data) throws BDecodeError { + this.root = new BDecoder(data, 0).decode(); + } +} diff --git a/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeError.java b/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeError.java new file mode 100644 index 0000000..d22d393 --- /dev/null +++ b/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeError.java @@ -0,0 +1,20 @@ +package com.mykola2312.retracker.bencode.error; + +public class BDecodeError extends Exception { + private static final long serialVersionUID = 4282658520481186036L; + + public byte[] data; + public int offset; + + public BDecodeError(byte[] data, int offset, String error) { + super(error); + this.data = data; + this.offset = offset; + } + + public BDecodeError(byte[] data, int offset) { + super(String.format("Error parsing data at offset %d", offset)); + this.data = data; + this.offset = offset; + } +} diff --git a/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeParseError.java b/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeParseError.java new file mode 100644 index 0000000..3ceedad --- /dev/null +++ b/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeParseError.java @@ -0,0 +1,10 @@ +package com.mykola2312.retracker.bencode.error; + +public class BDecodeParseError extends BDecodeError { + private static final long serialVersionUID = 8987586062482975916L; + + public BDecodeParseError(byte[] data, int offset, Throwable cause) { + super(data, offset, String.format("Failed to parse into primitive type at offset %d", offset)); + initCause(cause); + } +} diff --git a/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeUnknown.java b/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeUnknown.java new file mode 100644 index 0000000..6deb59d --- /dev/null +++ b/src/main/java/com/mykola2312/retracker/bencode/error/BDecodeUnknown.java @@ -0,0 +1,12 @@ +package com.mykola2312.retracker.bencode.error; + +public class BDecodeUnknown extends BDecodeError { + private static final long serialVersionUID = -7027240782119100714L; + + byte unknown; + + public BDecodeUnknown(byte[] data, int offset, byte unknown) { + super(data, offset, String.format("Unknown symbol 0x%x at offset %d", unknown)); + this.unknown = unknown; + } +} diff --git a/src/test/java/com/mykola2312/retracker/bencode/BTreeTest.java b/src/test/java/com/mykola2312/retracker/bencode/BTreeTest.java new file mode 100644 index 0000000..2f8ead0 --- /dev/null +++ b/src/test/java/com/mykola2312/retracker/bencode/BTreeTest.java @@ -0,0 +1,27 @@ +package com.mykola2312.retracker.bencode; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +import com.mykola2312.retracker.bencode.error.BDecodeError; + +public class BTreeTest { + @Test + public void testParseInt() throws BDecodeError { + final byte[] data = "i696969e".getBytes(); + + BTree tree = new BTree(); + assertDoesNotThrow(() -> { + tree.decode(data); + + BValue root = tree.getRoot(); + assertNotNull(root); + assertEquals(root, new BInteger(696969)); + + System.out.println("testParseInt: " + root.toString()); + }); + } +}