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