@li0ard/tinytlv
Version:
Tiny TLV decoder and encoder
161 lines (160 loc) • 4.72 kB
JavaScript
import { adjustTag, adjustValue, bytesToHex, getBufferLength, hexToBytes } from "./utils";
/** TLV class */
export class TLV {
/** Tag (as hex) */
tag;
/** Length */
length;
/** Value (as hex) */
value;
/** Value (as Uint8Array) */
byteValue;
/** Childs */
childs;
/** Parent object */
parent = null;
/** Is constructed or primitive? */
isConstructed;
/** Combined length of tag, length and value field */
size;
/**
* Initialize TLV object
* @param tag Tag
* @param value Value
* @example new TLV('84', '325041592E5359532E4444463031');
*/
constructor(tag, value) {
this.tag = adjustTag(tag);
this.value = adjustValue(value);
this.length = this.value.length / 2;
this.byteValue = hexToBytes(this.value);
this.childs = [];
let bTag = hexToBytes(this.tag);
this.size = bTag.length + getBufferLength(this.length).length + this.byteValue.length;
if ((bTag[0] & 0x20) === 0x00) {
this.isConstructed = false;
}
else {
this.isConstructed = true;
}
let offset = 0;
if (this.isConstructed) {
while (offset < this.length) {
let child = TLV.parse(this.byteValue.slice(offset));
child.parent = this;
offset += child.size;
this.childs.push(child);
}
}
}
/**
* Encode TLV as string
* @returns {string}
*/
toString() {
let byteLength = getBufferLength(this.length);
return `${this.tag}${bytesToHex(byteLength)}${this.value}`.toLowerCase();
}
/**
* Encode TLV as Uint8Array
* @returns {Uint8Array}
*/
toBytes() {
let byteLength = getBufferLength(this.length);
return hexToBytes(`${this.tag}${bytesToHex(byteLength)}${this.value}`);
}
/**
* Find `tag` in childs of main object
* @param tag Tag to find
* @returns {TLV | undefined}
*/
find(tag) {
let upperTAG = adjustTag(tag);
for (let child of this.childs) {
if (child.tag === upperTAG) {
return child;
}
if (child.isConstructed) {
let tlv = child.find(tag);
if (tlv !== undefined) {
return tlv;
}
}
}
}
/**
* Find all `tag`'s in childs of main object
* @param tag Tag to find
* @returns {TLV[]}
*/
findAll(tag) {
let upperTAG = adjustTag(tag);
let results = [];
for (let child of this.childs) {
if (child.tag === upperTAG) {
results.push(child);
}
if (child.isConstructed) {
let tlv = child.findAll(tag);
if (tlv.length !== 0) {
results = results.concat(tlv);
}
}
}
return results;
}
/**
* Parse TLV from string or Uint8Array
* @param data Data to parse
* @returns {TLV}
*/
static parse(data) {
let buf;
if (typeof data === 'string') {
buf = hexToBytes(data);
}
else {
buf = data;
}
let tag;
let tagLength = 1;
let length;
let byteLength;
if ((buf[0] & 0x1f) === 0x1f) {
let idx = 1;
do {
tagLength++;
if (idx > 4) {
throw Error("Invalid tag length");
}
} while ((buf[idx++] & 0x80) === 0x80);
}
tag = bytesToHex(buf.slice(0, tagLength));
let lenOffset = tagLength;
if ((buf[lenOffset] & 0x80) === 0x80) {
byteLength = (buf[lenOffset] & 0x7f) + 1;
}
else {
byteLength = 1;
}
if (byteLength === 1) {
length = buf[lenOffset];
}
else if (byteLength === 2) {
length = buf[lenOffset + 1];
}
else if (byteLength === 3) {
length = (buf[lenOffset + 1] << 8) | buf[lenOffset + 2];
}
else if (byteLength === 4) {
length = (buf[lenOffset + 1] << 16) | (buf[lenOffset + 2] << 8) | buf[lenOffset + 3];
}
else if (byteLength === 5) {
length = (buf[lenOffset + 1] << 24) | (buf[lenOffset + 2] << 16) | (buf[lenOffset + 3] << 8) | buf[lenOffset + 4];
}
else {
throw Error("Invalid length: " + byteLength);
}
return new TLV(tag, buf.slice(tagLength + byteLength, tagLength + byteLength + length));
}
}