layers_TLV.js

const { compile, alignOffset } = require('struct-compile');

/**
 * @typedef {Object} TLVOption
 * @property {number} type - The TLV option type.
 * @property {number} length - The length of the TLV option value.
 * @property {Buffer} value - The TLV option value.
 */

const { TLVRecord_8, TLVPadding_8 } = compile(`
  struct TLVRecord_8 {
    // Record type
    uint8_t type;
    // Record length in bytes
    uint8_t recLength;
    // Record value (variable size)
    uint8_t value[];
  };

  struct TLVPadding_8 {
    // Record type
    uint8_t type;
  };
`);

class TLV_8 extends TLVRecord_8 {
  constructor(data = {}, opts = {}) {
    super(data);
    if (!Buffer.isBuffer(data) && typeof data == 'object' && data?.value) {
      this.value = data.value;
      this._buf = Buffer.concat([this._buf, data.value]);
    }
    else {
      this.value = this._buf.subarray(this.length, this.recLength + (opts.lengthIsTotal ? 0 : this.length));
    }
    
    if (opts.lengthIsTotal) {
      this.length = this.recLength;
    }
    else {
      this.length += this.recLength;
    }
  }
};

class TLVIterator {
  constructor(Cls, buf, { skipTypes = [], lengthIsTotal = false } = {}) {
    this.Cls = Cls;
    this.buf = buf;
    this.pos = 0;
    this.skipTypes = skipTypes;
    this.lengthIsTotal = lengthIsTotal;
  }

  next() {
    try {
      if (this.pos < this.buf.length) {
        const res = new this.Cls(this.buf.subarray(this.pos), { lengthIsTotal: this.lengthIsTotal });
        if (this.skipTypes.includes(res.type)) {
          this.pos += 1;
          return { done: false, value: { type: res.type } };
        }
        else {
          this.pos += res.length;
          return { done: false, value: res.toObject() };
        }
      }
    } catch(err) {}
    return { done: true };
  }

  [Symbol.iterator]() {
    return this;
  }
}

function TLVSerialize(Cls, PaddingCls, opts, { skipTypes = [], lengthIsTotal = false, align = null } = {}) {
  let res = Buffer
    .concat(opts
      .map(e => skipTypes.includes(e.type) ? 
        new PaddingCls(e).buffer : 
        new Cls(e, { lengthIsTotal }).buffer
      )
    );

  if (align !== null && res.length % align > 0) {
    res = Buffer.concat([res, Buffer.alloc(alignOffset(res.length, align) - res.length)]);
  }

  return res;
}

function TLVLength(Cls, PaddingCls, opts, { skipTypes = [], lengthIsTotal = false, align = null } = {}) {
  let res = 0;

  for (const opt of opts) {
    if (skipTypes.includes(e.type)) {
      res += PaddingCls.prototype.config.length;
    }
    else {
      if (lengthIsTotal) {
        res += opt.recLength;
      }
      else {
        res += opt.recLength + Cls.prototype.config.length;
      }
    }
  }

  if (align !== null && res.length % align > 0) {
    res = alignOffset(res, align);
  }

  return res;
}

module.exports = { TLV_8, TLVPadding_8, TLVIterator, TLVSerialize, TLVLength };