From d8d594a493e299518db6dffc90d65cb0634f86b4 Mon Sep 17 00:00:00 2001
From: jg42526 <210032080+jg42526@users.noreply.github.com>
Date: Mon, 12 May 2025 15:24:44 +0000
Subject: [PATCH] Add Bencode function operation
---
package-lock.json | 37 ++++++++++++
package.json | 1 +
src/core/config/Categories.json | 4 +-
src/core/operations/BencodeDecode.mjs | 63 +++++++++++++++++++++
src/core/operations/BencodeEncode.mjs | 55 ++++++++++++++++++
tests/operations/index.mjs | 2 +
tests/operations/tests/BencodeDecode.mjs | 66 ++++++++++++++++++++++
tests/operations/tests/BencodeEncode.mjs | 72 ++++++++++++++++++++++++
8 files changed, 299 insertions(+), 1 deletion(-)
create mode 100644 src/core/operations/BencodeDecode.mjs
create mode 100644 src/core/operations/BencodeEncode.mjs
create mode 100644 tests/operations/tests/BencodeDecode.mjs
create mode 100644 tests/operations/tests/BencodeEncode.mjs
diff --git a/package-lock.json b/package-lock.json
index 8dd38b7a..974f9810 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"arrive": "^2.4.1",
"avsc": "^5.7.7",
"bcryptjs": "^2.4.3",
+ "bencodec": "^3.0.1",
"bignumber.js": "^9.1.2",
"blakejs": "^1.2.1",
"bootstrap": "4.6.2",
@@ -5403,6 +5404,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -5473,6 +5483,24 @@
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT"
},
+ "node_modules/bencode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/bencode/-/bencode-4.0.0.tgz",
+ "integrity": "sha512-AERXw18df0pF3ziGOCyUjqKZBVNH8HV3lBxnx5w0qtgMIk4a1wb9BkcCQbkp9Zstfrn/dzRwl7MmUHHocX3sRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "uint8-util": "^2.2.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/bencodec": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/bencodec/-/bencodec-3.0.1.tgz",
+ "integrity": "sha512-5Ntc3E7R1vSnBcOddG65L9kObEmZGI0Vool6z/7apwO5Hc9OlziwK0LyxvaTK5Il+nSWNxlVSuh2zJM+TN9O3g==",
+ "license": "MIT"
+ },
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -17850,6 +17878,15 @@
"integrity": "sha512-w+VZSp8hSZ/xWZfZNMppWNF6iqY+dcMYtG5CpwRDgxi94HIE6ematSdkzHGzVC4SDEaTsG65zrajN+oKoWG6ew==",
"license": "MIT"
},
+ "node_modules/uint8-util": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/uint8-util/-/uint8-util-2.2.5.tgz",
+ "integrity": "sha512-/QxVQD7CttWpVUKVPz9znO+3Dd4BdTSnFQ7pv/4drVhC9m4BaL2LFHTkJn6EsYoxT79VDq/2Gg8L0H22PrzyMw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
diff --git a/package.json b/package.json
index feb18bc8..4b116619 100644
--- a/package.json
+++ b/package.json
@@ -105,6 +105,7 @@
"arrive": "^2.4.1",
"avsc": "^5.7.7",
"bcryptjs": "^2.4.3",
+ "bencodec": "^3.0.1",
"bignumber.js": "^9.1.2",
"blakejs": "^1.2.1",
"bootstrap": "4.6.2",
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 7f9591e0..a44b65a7 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -162,7 +162,9 @@
"Typex",
"Lorenz",
"Colossus",
- "SIGABA"
+ "SIGABA",
+ "Bencode Encode",
+ "Bencode Decode"
]
},
{
diff --git a/src/core/operations/BencodeDecode.mjs b/src/core/operations/BencodeDecode.mjs
new file mode 100644
index 00000000..a4a680bf
--- /dev/null
+++ b/src/core/operations/BencodeDecode.mjs
@@ -0,0 +1,63 @@
+/**
+ * @author jg42526
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import bencodec from "bencodec";
+
+/**
+ * URL Decode operation
+ */
+class BencodeDecode extends Operation {
+
+ /**
+ * URLDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Bencode Decode";
+ this.module = "Encodings";
+ this.description = "Decodes a Bencoded string.
e.g. 7:bencode
becomes bencode
";
+ this.infoURL = "https://en.wikipedia.org/wiki/Bencode";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ if (input) return toStringRepresentation(bencodec.decode(input, { stringify: true }));
+ return "";
+ }
+
+}
+
+export default BencodeDecode;
+
+/**
+ * Returns string representation of object
+ */
+function toStringRepresentation(value) {
+ if (typeof value === "string") {
+ return value;
+ }
+
+ if (typeof value === "number" || typeof value === "boolean") {
+ return String(value);
+ }
+
+ if (Array.isArray(value) || (value !== null && typeof value === "object")) {
+ // For arrays and objects, output JSON string
+ return JSON.stringify(value);
+ }
+
+ // For other types (undefined, null), handle as you see fit, e.g.:
+ return String(value);
+}
diff --git a/src/core/operations/BencodeEncode.mjs b/src/core/operations/BencodeEncode.mjs
new file mode 100644
index 00000000..91d519ed
--- /dev/null
+++ b/src/core/operations/BencodeEncode.mjs
@@ -0,0 +1,55 @@
+/**
+ * @author jg42526
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import bencodec from "bencodec";
+
+/**
+ * URL Decode operation
+ */
+class BencodeEncode extends Operation {
+
+ /**
+ * URLDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Bencode Encode";
+ this.module = "Encodings";
+ this.description = "Bencodes a string.
e.g. bencode
becomes 7:bencode
";
+ this.infoURL = "https://en.wikipedia.org/wiki/Bencode";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ return bencodec.encode(parseValue(input), { stringify: true });
+ }
+
+}
+
+export default BencodeEncode;
+
+/**
+ * Parses string, returns appropraite data structure
+ */
+function parseValue(str) {
+ const trimmed = str.trim();
+ try {
+ // Attempt to parse with JSON.parse
+ return JSON.parse(trimmed);
+ } catch (e) {
+ // If JSON.parse fails, treat input as a plain string (assuming it's unquoted)
+ return trimmed;
+ }
+}
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index ab1ceb8f..8d195553 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -26,6 +26,8 @@ import "./tests/Base64.mjs";
import "./tests/Base85.mjs";
import "./tests/Base92.mjs";
import "./tests/BCD.mjs";
+import "./tests/BencodeEncode.mjs";
+import "./tests/BencodeDecode.mjs";
import "./tests/BitwiseOp.mjs";
import "./tests/BLAKE2b.mjs";
import "./tests/BLAKE2s.mjs";
diff --git a/tests/operations/tests/BencodeDecode.mjs b/tests/operations/tests/BencodeDecode.mjs
new file mode 100644
index 00000000..c0297d8b
--- /dev/null
+++ b/tests/operations/tests/BencodeDecode.mjs
@@ -0,0 +1,66 @@
+/**
+ * Bencode Encode tests.
+ *
+ * @author jg42526
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Bencode Decode: nothing",
+ input: "",
+ expectedOutput: "",
+ recipeConfig: [
+ {
+ "op": "Bencode Decode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Decode: integer",
+ input: "i42e",
+ expectedOutput: "42",
+ recipeConfig: [
+ {
+ "op": "Bencode Decode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Decode: byte string",
+ input: "7:bencode",
+ expectedOutput: "bencode",
+ recipeConfig: [
+ {
+ "op": "Bencode Decode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Decode: list",
+ input: "l7:bencodei-20ee",
+ expectedOutput: `["bencode",-20]`,
+ recipeConfig: [
+ {
+ "op": "Bencode Decode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Decode: dictionary",
+ input: "d7:meaningi42e4:wiki7:bencodee",
+ expectedOutput: `{"meaning":42,"wiki":"bencode"}`,
+ recipeConfig: [
+ {
+ "op": "Bencode Decode",
+ "args": []
+ }
+ ]
+ },
+]);
diff --git a/tests/operations/tests/BencodeEncode.mjs b/tests/operations/tests/BencodeEncode.mjs
new file mode 100644
index 00000000..51b96826
--- /dev/null
+++ b/tests/operations/tests/BencodeEncode.mjs
@@ -0,0 +1,72 @@
+/**
+ * Bencode Encode tests.
+ *
+ * @author jg42526
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Bencode Encode: nothing",
+ input: "",
+ expectedOutput: "0:",
+ recipeConfig: [
+ {
+ "op": "Bencode Encode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Encode: integer",
+ input: "42",
+ expectedOutput: "i42e",
+ recipeConfig: [
+ {
+ "op": "Bencode Encode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Encode: byte string",
+ input: "bencode",
+ expectedOutput: "7:bencode",
+ recipeConfig: [
+ {
+ "op": "Bencode Encode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Encode: list",
+ input: `[
+ "bencode",
+ -20
+ ]`,
+ expectedOutput: "l7:bencodei-20ee",
+ recipeConfig: [
+ {
+ "op": "Bencode Encode",
+ "args": []
+ }
+ ]
+ },
+ {
+ name: "Bencode Encode: dictionary",
+ input: `{
+ "meaning": 42,
+ "wiki": "bencode"
+ }`,
+ expectedOutput: "d7:meaningi42e4:wiki7:bencodee",
+ recipeConfig: [
+ {
+ "op": "Bencode Encode",
+ "args": []
+ }
+ ]
+ },
+]);