diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index ca762f1d..6f02abb8 100755
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -53,7 +53,8 @@
"To MessagePack",
"From MessagePack",
"To Braille",
- "From Braille"
+ "From Braille",
+ "Parse TLV"
]
},
{
diff --git a/src/core/lib/TLVParser.mjs b/src/core/lib/TLVParser.mjs
new file mode 100644
index 00000000..9e9395ff
--- /dev/null
+++ b/src/core/lib/TLVParser.mjs
@@ -0,0 +1,78 @@
+/**
+ * Parser for Type-length-value data.
+ *
+ * @author gchq77703 []
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+const defaults = {
+ location: 0,
+ bytesInLength: 1,
+ basicEncodingRules: false
+};
+
+/**
+ * TLVParser library
+ */
+export default class TLVParser {
+
+ /**
+ * TLVParser constructor
+ *
+ * @param {byteArray} input
+ * @param {Object} options
+ */
+ constructor(input, options) {
+ this.input = input;
+ Object.assign(this, defaults, options);
+ }
+
+ /**
+ * @returns {number}
+ */
+ getLength() {
+ if (this.basicEncodingRules) {
+ const bit = this.input[this.location];
+ if (bit & 0x80) {
+ this.bytesInLength = bit & ~0x80;
+ } else {
+ this.location++;
+ return bit & ~0x80;
+ }
+ }
+
+ let length = 0;
+
+ for (let i = 0; i < this.bytesInLength; i++) {
+ length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
+ this.location++;
+ }
+
+ return length;
+ }
+
+ /**
+ * @param {number} length
+ * @returns {number[]}
+ */
+ getValue(length) {
+ const value = [];
+
+ for (let i = 0; i < length; i++) {
+ if (this.location > this.input.length) return value;
+ value.push(this.input[this.location]);
+ this.location++;
+ }
+
+ return value;
+ }
+
+ /**
+ * @returns {boolean}
+ */
+ atEnd() {
+ return this.input.length <= this.location;
+ }
+}
diff --git a/src/core/operations/ParseTLV.mjs b/src/core/operations/ParseTLV.mjs
new file mode 100644
index 00000000..a87144a8
--- /dev/null
+++ b/src/core/operations/ParseTLV.mjs
@@ -0,0 +1,76 @@
+/**
+ * @author gchq77703 []
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import TLVParser from "../lib/TLVParser";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Parse TLV operation
+ */
+class ParseTLV extends Operation {
+
+ /**
+ * ParseTLV constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Parse TLV";
+ this.module = "Default";
+ this.description = "Converts a Type-Length-Value (TLV) encoded string into a JSON object. Can optionally include a Key
/ Type
entry.
Tags: Key-Length-Value, KLV, Length-Value, LV";
+ this.infoURL = "https://wikipedia.org/wiki/Type-length-value";
+ this.inputType = "byteArray";
+ this.outputType = "JSON";
+ this.args = [
+ {
+ name: "Type/Key size",
+ type: "number",
+ value: 1
+ },
+ {
+ name: "Length size",
+ type: "number",
+ value: 1
+ },
+ {
+ name: "Use BER",
+ type: "boolean",
+ value: false
+ }
+ ];
+ }
+
+ /**
+ * @param {byteArray} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [bytesInKey, bytesInLength, basicEncodingRules] = args;
+
+ if (bytesInKey <= 0 && bytesInLength <= 0)
+ throw new OperationError("Type or Length size must be greater than 0");
+
+ const tlv = new TLVParser(input, { bytesInLength, basicEncodingRules });
+
+ const data = [];
+
+ while (!tlv.atEnd()) {
+ const key = bytesInKey ? tlv.getValue(bytesInKey) : undefined;
+ const length = tlv.getLength();
+ const value = tlv.getValue(length);
+
+ data.push({ key, length, value });
+ }
+
+ return data;
+ }
+
+}
+
+export default ParseTLV;
diff --git a/test/index.mjs b/test/index.mjs
index 3f873201..9bb93a60 100644
--- a/test/index.mjs
+++ b/test/index.mjs
@@ -71,6 +71,7 @@ import "./tests/operations/SymmetricDifference";
import "./tests/operations/ToGeohash.mjs";
import "./tests/operations/TranslateDateTimeFormat";
import "./tests/operations/Magic";
+import "./tests/operations/ParseTLV";
let allTestsPassing = true;
const testStatusCounts = {
diff --git a/test/tests/operations/ParseTLV.mjs b/test/tests/operations/ParseTLV.mjs
new file mode 100644
index 00000000..43cc02d7
--- /dev/null
+++ b/test/tests/operations/ParseTLV.mjs
@@ -0,0 +1,56 @@
+/**
+ * Parse TLV tests.
+ *
+ * @author gchq77703 []
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../TestRegister";
+
+TestRegister.addTests([
+ {
+ name: "Parse TLV: LengthValue",
+ input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72",
+ expectedOutput: JSON.stringify([{"length": 5, "value": [72, 111, 117, 115, 101]}, {"length": 4, "value": [114, 111, 111, 109]}, {"length": 4, "value": [100, 111, 111, 114]}], null, 4),
+ recipeConfig: [
+ {
+ "op": "Parse TLV",
+ "args": [0, 1, false]
+ }
+ ]
+ },
+ {
+ name: "Parse TLV: LengthValue with BER",
+ input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72",
+ expectedOutput: JSON.stringify([{"length": 5, "value": [72, 111, 117, 115, 101]}, {"length": 4, "value": [114, 111, 111, 109]}, {"length": 4, "value": [100, 111, 111, 114]}], null, 4),
+ recipeConfig: [
+ {
+ "op": "Parse TLV",
+ "args": [0, 4, true] // length value is patently wrong, should be ignored by BER.
+ }
+ ]
+ },
+ {
+ name: "Parse TLV: KeyLengthValue",
+ input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72",
+ expectedOutput: JSON.stringify([{"key": [4], "length": 5, "value": [72, 111, 117, 115, 101]}, {"key": [5], "length": 4, "value": [114, 111, 111, 109]}, {"key": [66], "length": 4, "value": [100, 111, 111, 114]}], null, 4),
+ recipeConfig: [
+ {
+ "op": "Parse TLV",
+ "args": [1, 1, false]
+ }
+ ]
+ },
+ {
+ name: "Parse TLV: KeyLengthValue with BER",
+ input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72",
+ expectedOutput: JSON.stringify([{"key": [4], "length": 5, "value": [72, 111, 117, 115, 101]}, {"key": [5], "length": 4, "value": [114, 111, 111, 109]}, {"key": [66], "length": 4, "value": [100, 111, 111, 114]}], null, 4),
+ recipeConfig: [
+ {
+ "op": "Parse TLV",
+ "args": [1, 4, true] // length value is patently wrong, should be ignored by BER.
+ }
+ ]
+ }
+]);