Skip to content

Commit 14309f2

Browse files
committed
Merge branch 'GCHQ77703-tlv'
2 parents ec9dfd2 + e6b89d5 commit 14309f2

File tree

5 files changed

+213
-1
lines changed

5 files changed

+213
-1
lines changed

src/core/config/Categories.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
"To MessagePack",
5454
"From MessagePack",
5555
"To Braille",
56-
"From Braille"
56+
"From Braille",
57+
"Parse TLV"
5758
]
5859
},
5960
{

src/core/lib/TLVParser.mjs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* Parser for Type-length-value data.
3+
*
4+
* @author gchq77703 []
5+
* @author n1474335 [[email protected]]
6+
* @copyright Crown Copyright 2018
7+
* @license Apache-2.0
8+
*/
9+
10+
const defaults = {
11+
location: 0,
12+
bytesInLength: 1,
13+
basicEncodingRules: false
14+
};
15+
16+
/**
17+
* TLVParser library
18+
*/
19+
export default class TLVParser {
20+
21+
/**
22+
* TLVParser constructor
23+
*
24+
* @param {byteArray} input
25+
* @param {Object} options
26+
*/
27+
constructor(input, options) {
28+
this.input = input;
29+
Object.assign(this, defaults, options);
30+
}
31+
32+
/**
33+
* @returns {number}
34+
*/
35+
getLength() {
36+
if (this.basicEncodingRules) {
37+
const bit = this.input[this.location];
38+
if (bit & 0x80) {
39+
this.bytesInLength = bit & ~0x80;
40+
} else {
41+
this.location++;
42+
return bit & ~0x80;
43+
}
44+
}
45+
46+
let length = 0;
47+
48+
for (let i = 0; i < this.bytesInLength; i++) {
49+
length += this.input[this.location] * Math.pow(Math.pow(2, 8), i);
50+
this.location++;
51+
}
52+
53+
return length;
54+
}
55+
56+
/**
57+
* @param {number} length
58+
* @returns {number[]}
59+
*/
60+
getValue(length) {
61+
const value = [];
62+
63+
for (let i = 0; i < length; i++) {
64+
if (this.location > this.input.length) return value;
65+
value.push(this.input[this.location]);
66+
this.location++;
67+
}
68+
69+
return value;
70+
}
71+
72+
/**
73+
* @returns {boolean}
74+
*/
75+
atEnd() {
76+
return this.input.length <= this.location;
77+
}
78+
}

src/core/operations/ParseTLV.mjs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @author gchq77703 []
3+
* @author n1474335 [[email protected]]
4+
* @copyright Crown Copyright 2018
5+
* @license Apache-2.0
6+
*/
7+
8+
import Operation from "../Operation";
9+
import TLVParser from "../lib/TLVParser";
10+
import OperationError from "../errors/OperationError";
11+
12+
/**
13+
* Parse TLV operation
14+
*/
15+
class ParseTLV extends Operation {
16+
17+
/**
18+
* ParseTLV constructor
19+
*/
20+
constructor() {
21+
super();
22+
23+
this.name = "Parse TLV";
24+
this.module = "Default";
25+
this.description = "Converts a Type-Length-Value (TLV) encoded string into a JSON object. Can optionally include a <code>Key</code> / <code>Type</code> entry. <br><br>Tags: Key-Length-Value, KLV, Length-Value, LV";
26+
this.infoURL = "https://wikipedia.org/wiki/Type-length-value";
27+
this.inputType = "byteArray";
28+
this.outputType = "JSON";
29+
this.args = [
30+
{
31+
name: "Type/Key size",
32+
type: "number",
33+
value: 1
34+
},
35+
{
36+
name: "Length size",
37+
type: "number",
38+
value: 1
39+
},
40+
{
41+
name: "Use BER",
42+
type: "boolean",
43+
value: false
44+
}
45+
];
46+
}
47+
48+
/**
49+
* @param {byteArray} input
50+
* @param {Object[]} args
51+
* @returns {string}
52+
*/
53+
run(input, args) {
54+
const [bytesInKey, bytesInLength, basicEncodingRules] = args;
55+
56+
if (bytesInKey <= 0 && bytesInLength <= 0)
57+
throw new OperationError("Type or Length size must be greater than 0");
58+
59+
const tlv = new TLVParser(input, { bytesInLength, basicEncodingRules });
60+
61+
const data = [];
62+
63+
while (!tlv.atEnd()) {
64+
const key = bytesInKey ? tlv.getValue(bytesInKey) : undefined;
65+
const length = tlv.getLength();
66+
const value = tlv.getValue(length);
67+
68+
data.push({ key, length, value });
69+
}
70+
71+
return data;
72+
}
73+
74+
}
75+
76+
export default ParseTLV;

test/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import "./tests/operations/SymmetricDifference";
7171
import "./tests/operations/ToGeohash.mjs";
7272
import "./tests/operations/TranslateDateTimeFormat";
7373
import "./tests/operations/Magic";
74+
import "./tests/operations/ParseTLV";
7475

7576
let allTestsPassing = true;
7677
const testStatusCounts = {

test/tests/operations/ParseTLV.mjs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Parse TLV tests.
3+
*
4+
* @author gchq77703 []
5+
* @copyright Crown Copyright 2018
6+
* @license Apache-2.0
7+
*/
8+
9+
import TestRegister from "../../TestRegister";
10+
11+
TestRegister.addTests([
12+
{
13+
name: "Parse TLV: LengthValue",
14+
input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72",
15+
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),
16+
recipeConfig: [
17+
{
18+
"op": "Parse TLV",
19+
"args": [0, 1, false]
20+
}
21+
]
22+
},
23+
{
24+
name: "Parse TLV: LengthValue with BER",
25+
input: "\x05\x48\x6f\x75\x73\x65\x04\x72\x6f\x6f\x6d\x04\x64\x6f\x6f\x72",
26+
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),
27+
recipeConfig: [
28+
{
29+
"op": "Parse TLV",
30+
"args": [0, 4, true] // length value is patently wrong, should be ignored by BER.
31+
}
32+
]
33+
},
34+
{
35+
name: "Parse TLV: KeyLengthValue",
36+
input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72",
37+
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),
38+
recipeConfig: [
39+
{
40+
"op": "Parse TLV",
41+
"args": [1, 1, false]
42+
}
43+
]
44+
},
45+
{
46+
name: "Parse TLV: KeyLengthValue with BER",
47+
input: "\x04\x05\x48\x6f\x75\x73\x65\x05\x04\x72\x6f\x6f\x6d\x42\x04\x64\x6f\x6f\x72",
48+
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),
49+
recipeConfig: [
50+
{
51+
"op": "Parse TLV",
52+
"args": [1, 4, true] // length value is patently wrong, should be ignored by BER.
53+
}
54+
]
55+
}
56+
]);

0 commit comments

Comments
 (0)