"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.fromBER = fromBER;
exports.compareSchema = compareSchema;
exports.verifySchema = verifySchema;
exports.fromJSON = fromJSON;
exports.RawData = exports.Repeated = exports.Any = exports.Choice = exports.TIME = exports.Duration = exports.DateTime = exports.TimeOfDay = exports.DATE = exports.GeneralizedTime = exports.UTCTime = exports.CharacterString = exports.GeneralString = exports.VisibleString = exports.GraphicString = exports.IA5String = exports.VideotexString = exports.TeletexString = exports.PrintableString = exports.NumericString = exports.UniversalString = exports.BmpString = exports.RelativeObjectIdentifier = exports.Utf8String = exports.ObjectIdentifier = exports.Enumerated = exports.Integer = exports.BitString = exports.OctetString = exports.Null = exports.Set = exports.Sequence = exports.Boolean = exports.EndOfContent = exports.Constructed = exports.Primitive = exports.BaseBlock = exports.ValueBlock = exports.HexBlock = void 0;

var _pvutils = require("pvutils");

/* eslint-disable indent */

/*
 * Copyright (c) 2016-2018, Peculiar Ventures
 * All rights reserved.
 *
 * Author 2016-2018, Yury Strozhevsky <www.strozhevsky.com>.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 */
//**************************************************************************************
//**************************************************************************************
//region Declaration of global variables
//**************************************************************************************
const powers2 = [new Uint8Array([1])];
const digitsString = "0123456789"; //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration for "LocalBaseBlock" class
//**************************************************************************************

/**
 * Class used as a base block for all remaining ASN.1 classes
 * @typedef LocalBaseBlock
 * @interface
 * @property {number} blockLength
 * @property {string} error
 * @property {Array.<string>} warnings
 * @property {ArrayBuffer} valueBeforeDecode
 */

class LocalBaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalBaseBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueBeforeDecode]
   */
  constructor(parameters = {}) {
    /**
     * @type {number} blockLength
     */
    this.blockLength = (0, _pvutils.getParametersValue)(parameters, "blockLength", 0);
    /**
     * @type {string} error
     */

    this.error = (0, _pvutils.getParametersValue)(parameters, "error", "");
    /**
     * @type {Array.<string>} warnings
     */

    this.warnings = (0, _pvutils.getParametersValue)(parameters, "warnings", []); //noinspection JSCheckFunctionSignatures

    /**
     * @type {ArrayBuffer} valueBeforeDecode
     */

    if ("valueBeforeDecode" in parameters) this.valueBeforeDecode = parameters.valueBeforeDecode.slice(0);else this.valueBeforeDecode = new ArrayBuffer(0);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "baseBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    return {
      blockName: this.constructor.blockName(),
      blockLength: this.blockLength,
      error: this.error,
      warnings: this.warnings,
      valueBeforeDecode: (0, _pvutils.bufferToHexCodes)(this.valueBeforeDecode, 0, this.valueBeforeDecode.byteLength)
    };
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Description for "HexBlock" class
//**************************************************************************************

/**
 * Class used as a base block for all remaining ASN.1 classes
 * @extends LocalBaseBlock
 * @typedef HexBlock
 * @property {number} blockLength
 * @property {string} error
 * @property {Array.<string>} warnings
 * @property {ArrayBuffer} valueBeforeDecode
 * @property {boolean} isHexOnly
 * @property {ArrayBuffer} valueHex
 */
//noinspection JSUnusedLocalSymbols


const HexBlock = BaseClass => class LocalHexBlockMixin extends BaseClass {
  //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Constructor for "HexBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters);
    /**
     * @type {boolean}
     */

    this.isHexOnly = (0, _pvutils.getParametersValue)(parameters, "isHexOnly", false);
    /**
     * @type {ArrayBuffer}
     */

    if ("valueHex" in parameters) this.valueHex = parameters.valueHex.slice(0);else this.valueHex = new ArrayBuffer(0);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "hexBlock";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures
    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion
    //region Getting Uint8Array from ArrayBuffer

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion
    //region Initial checks

    if (intBuffer.length === 0) {
      this.warnings.push("Zero buffer length");
      return inputOffset;
    } //endregion
    //region Copy input buffer to internal buffer


    this.valueHex = inputBuffer.slice(inputOffset, inputOffset + inputLength); //endregion

    this.blockLength = inputLength;
    return inputOffset + inputLength;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    if (this.isHexOnly !== true) {
      this.error = "Flag \"isHexOnly\" is not set, abort";
      return new ArrayBuffer(0);
    }

    if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength); //noinspection JSCheckFunctionSignatures

    return this.valueHex.slice(0);
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.blockName = this.constructor.blockName();
    object.isHexOnly = this.isHexOnly;
    object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);
    return object;
  } //**********************************************************************************


}; //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of identification block class
//**************************************************************************************


exports.HexBlock = HexBlock;

class LocalIdentificationBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalBaseBlock" class
   * @param {Object} [parameters={}]
   * @property {Object} [idBlock]
   */
  constructor(parameters = {}) {
    super();

    if ("idBlock" in parameters) {
      //region Properties from hexBlock class
      this.isHexOnly = (0, _pvutils.getParametersValue)(parameters.idBlock, "isHexOnly", false);
      this.valueHex = (0, _pvutils.getParametersValue)(parameters.idBlock, "valueHex", new ArrayBuffer(0)); //endregion

      this.tagClass = (0, _pvutils.getParametersValue)(parameters.idBlock, "tagClass", -1);
      this.tagNumber = (0, _pvutils.getParametersValue)(parameters.idBlock, "tagNumber", -1);
      this.isConstructed = (0, _pvutils.getParametersValue)(parameters.idBlock, "isConstructed", false);
    } else {
      this.tagClass = -1;
      this.tagNumber = -1;
      this.isConstructed = false;
    }
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "identificationBlock";
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    //region Initial variables
    let firstOctet = 0;
    let retBuf;
    let retView; //endregion

    switch (this.tagClass) {
      case 1:
        firstOctet |= 0x00; // UNIVERSAL

        break;

      case 2:
        firstOctet |= 0x40; // APPLICATION

        break;

      case 3:
        firstOctet |= 0x80; // CONTEXT-SPECIFIC

        break;

      case 4:
        firstOctet |= 0xC0; // PRIVATE

        break;

      default:
        this.error = "Unknown tag class";
        return new ArrayBuffer(0);
    }

    if (this.isConstructed) firstOctet |= 0x20;

    if (this.tagNumber < 31 && !this.isHexOnly) {
      retBuf = new ArrayBuffer(1);
      retView = new Uint8Array(retBuf);

      if (!sizeOnly) {
        let number = this.tagNumber;
        number &= 0x1F;
        firstOctet |= number;
        retView[0] = firstOctet;
      }

      return retBuf;
    }

    if (this.isHexOnly === false) {
      const encodedBuf = (0, _pvutils.utilToBase)(this.tagNumber, 7);
      const encodedView = new Uint8Array(encodedBuf);
      const size = encodedBuf.byteLength;
      retBuf = new ArrayBuffer(size + 1);
      retView = new Uint8Array(retBuf);
      retView[0] = firstOctet | 0x1F;

      if (!sizeOnly) {
        for (let i = 0; i < size - 1; i++) retView[i + 1] = encodedView[i] | 0x80;

        retView[size] = encodedView[size - 1];
      }

      return retBuf;
    }

    retBuf = new ArrayBuffer(this.valueHex.byteLength + 1);
    retView = new Uint8Array(retBuf);
    retView[0] = firstOctet | 0x1F;

    if (sizeOnly === false) {
      const curView = new Uint8Array(this.valueHex);

      for (let i = 0; i < curView.length - 1; i++) retView[i + 1] = curView[i] | 0x80;

      retView[this.valueHex.byteLength] = curView[curView.length - 1];
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures
    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion
    //region Getting Uint8Array from ArrayBuffer

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion
    //region Initial checks

    if (intBuffer.length === 0) {
      this.error = "Zero buffer length";
      return -1;
    } //endregion
    //region Find tag class


    const tagClassMask = intBuffer[0] & 0xC0;

    switch (tagClassMask) {
      case 0x00:
        this.tagClass = 1; // UNIVERSAL

        break;

      case 0x40:
        this.tagClass = 2; // APPLICATION

        break;

      case 0x80:
        this.tagClass = 3; // CONTEXT-SPECIFIC

        break;

      case 0xC0:
        this.tagClass = 4; // PRIVATE

        break;

      default:
        this.error = "Unknown tag class";
        return -1;
    } //endregion
    //region Find it's constructed or not


    this.isConstructed = (intBuffer[0] & 0x20) === 0x20; //endregion
    //region Find tag number

    this.isHexOnly = false;
    const tagNumberMask = intBuffer[0] & 0x1F; //region Simple case (tag number < 31)

    if (tagNumberMask !== 0x1F) {
      this.tagNumber = tagNumberMask;
      this.blockLength = 1;
    } //endregion
    //region Tag number bigger or equal to 31
    else {
        let count = 1;
        this.valueHex = new ArrayBuffer(255);
        let tagNumberBufferMaxLength = 255;
        let intTagNumberBuffer = new Uint8Array(this.valueHex); //noinspection JSBitwiseOperatorUsage

        while (intBuffer[count] & 0x80) {
          intTagNumberBuffer[count - 1] = intBuffer[count] & 0x7F;
          count++;

          if (count >= intBuffer.length) {
            this.error = "End of input reached before message was fully decoded";
            return -1;
          } //region In case if tag number length is greater than 255 bytes (rare but possible case)


          if (count === tagNumberBufferMaxLength) {
            tagNumberBufferMaxLength += 255;
            const tempBuffer = new ArrayBuffer(tagNumberBufferMaxLength);
            const tempBufferView = new Uint8Array(tempBuffer);

            for (let i = 0; i < intTagNumberBuffer.length; i++) tempBufferView[i] = intTagNumberBuffer[i];

            this.valueHex = new ArrayBuffer(tagNumberBufferMaxLength);
            intTagNumberBuffer = new Uint8Array(this.valueHex);
          } //endregion

        }

        this.blockLength = count + 1;
        intTagNumberBuffer[count - 1] = intBuffer[count] & 0x7F; // Write last byte to buffer
        //region Cut buffer

        const tempBuffer = new ArrayBuffer(count);
        const tempBufferView = new Uint8Array(tempBuffer);

        for (let i = 0; i < count; i++) tempBufferView[i] = intTagNumberBuffer[i];

        this.valueHex = new ArrayBuffer(count);
        intTagNumberBuffer = new Uint8Array(this.valueHex);
        intTagNumberBuffer.set(tempBufferView); //endregion
        //region Try to convert long tag number to short form

        if (this.blockLength <= 9) this.tagNumber = (0, _pvutils.utilFromBase)(intTagNumberBuffer, 7);else {
          this.isHexOnly = true;
          this.warnings.push("Tag too long, represented as hex-coded");
        } //endregion
      } //endregion
    //endregion
    //region Check if constructed encoding was using for primitive type


    if (this.tagClass === 1 && this.isConstructed) {
      switch (this.tagNumber) {
        case 1: // Boolean

        case 2: // REAL

        case 5: // Null

        case 6: // OBJECT IDENTIFIER

        case 9: // REAL

        case 13: // RELATIVE OBJECT IDENTIFIER

        case 14: // Time

        case 23:
        case 24:
        case 31:
        case 32:
        case 33:
        case 34:
          this.error = "Constructed encoding used for primitive type";
          return -1;

        default:
      }
    } //endregion


    return inputOffset + this.blockLength; // Return current offset in input buffer
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName: string,
   *  tagClass: number,
   *  tagNumber: number,
   *  isConstructed: boolean,
   *  isHexOnly: boolean,
   *  valueHex: ArrayBuffer,
   *  blockLength: number,
   *  error: string, warnings: Array.<string>,
   *  valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.blockName = this.constructor.blockName();
    object.tagClass = this.tagClass;
    object.tagNumber = this.tagNumber;
    object.isConstructed = this.isConstructed;
    return object;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of length block class
//**************************************************************************************


class LocalLengthBlock extends LocalBaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalLengthBlock" class
   * @param {Object} [parameters={}]
   * @property {Object} [lenBlock]
   */
  constructor(parameters = {}) {
    super();

    if ("lenBlock" in parameters) {
      this.isIndefiniteForm = (0, _pvutils.getParametersValue)(parameters.lenBlock, "isIndefiniteForm", false);
      this.longFormUsed = (0, _pvutils.getParametersValue)(parameters.lenBlock, "longFormUsed", false);
      this.length = (0, _pvutils.getParametersValue)(parameters.lenBlock, "length", 0);
    } else {
      this.isIndefiniteForm = false;
      this.longFormUsed = false;
      this.length = 0;
    }
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "lengthBlock";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures
    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion
    //region Getting Uint8Array from ArrayBuffer

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion
    //region Initial checks

    if (intBuffer.length === 0) {
      this.error = "Zero buffer length";
      return -1;
    }

    if (intBuffer[0] === 0xFF) {
      this.error = "Length block 0xFF is reserved by standard";
      return -1;
    } //endregion
    //region Check for length form type


    this.isIndefiniteForm = intBuffer[0] === 0x80; //endregion
    //region Stop working in case of indefinite length form

    if (this.isIndefiniteForm === true) {
      this.blockLength = 1;
      return inputOffset + this.blockLength;
    } //endregion
    //region Check is long form of length encoding using


    this.longFormUsed = !!(intBuffer[0] & 0x80); //endregion
    //region Stop working in case of short form of length value

    if (this.longFormUsed === false) {
      this.length = intBuffer[0];
      this.blockLength = 1;
      return inputOffset + this.blockLength;
    } //endregion
    //region Calculate length value in case of long form


    const count = intBuffer[0] & 0x7F;

    if (count > 8) // Too big length value
      {
        this.error = "Too big integer";
        return -1;
      }

    if (count + 1 > intBuffer.length) {
      this.error = "End of input reached before message was fully decoded";
      return -1;
    }

    const lengthBufferView = new Uint8Array(count);

    for (let i = 0; i < count; i++) lengthBufferView[i] = intBuffer[i + 1];

    if (lengthBufferView[count - 1] === 0x00) this.warnings.push("Needlessly long encoded length");
    this.length = (0, _pvutils.utilFromBase)(lengthBufferView, 8);
    if (this.longFormUsed && this.length <= 127) this.warnings.push("Unneccesary usage of long length form");
    this.blockLength = count + 1; //endregion

    return inputOffset + this.blockLength; // Return current offset in input buffer
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    //region Initial variables
    let retBuf;
    let retView; //endregion

    if (this.length > 127) this.longFormUsed = true;

    if (this.isIndefiniteForm) {
      retBuf = new ArrayBuffer(1);

      if (sizeOnly === false) {
        retView = new Uint8Array(retBuf);
        retView[0] = 0x80;
      }

      return retBuf;
    }

    if (this.longFormUsed === true) {
      const encodedBuf = (0, _pvutils.utilToBase)(this.length, 8);

      if (encodedBuf.byteLength > 127) {
        this.error = "Too big length";
        return new ArrayBuffer(0);
      }

      retBuf = new ArrayBuffer(encodedBuf.byteLength + 1);
      if (sizeOnly === true) return retBuf;
      const encodedView = new Uint8Array(encodedBuf);
      retView = new Uint8Array(retBuf);
      retView[0] = encodedBuf.byteLength | 0x80;

      for (let i = 0; i < encodedBuf.byteLength; i++) retView[i + 1] = encodedView[i];

      return retBuf;
    }

    retBuf = new ArrayBuffer(1);

    if (sizeOnly === false) {
      retView = new Uint8Array(retBuf);
      retView[0] = this.length;
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.blockName = this.constructor.blockName();
    object.isIndefiniteForm = this.isIndefiniteForm;
    object.longFormUsed = this.longFormUsed;
    object.length = this.length;
    return object;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of value block class
//**************************************************************************************


class ValueBlock extends LocalBaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "ValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "valueBlock";
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols,JSUnusedLocalSymbols,JSUnusedLocalSymbols

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Throw an exception for a function which needs to be specified in extended classes
    throw TypeError("User need to make a specific function in a class which extends \"ValueBlock\""); //endregion
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    //region Throw an exception for a function which needs to be specified in extended classes
    throw TypeError("User need to make a specific function in a class which extends \"ValueBlock\""); //endregion
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of basic ASN.1 block class
//**************************************************************************************


exports.ValueBlock = ValueBlock;

class BaseBlock extends LocalBaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "BaseBlock" class
   * @param {Object} [parameters={}]
   * @property {Object} [primitiveSchema]
   * @property {string} [name]
   * @property {boolean} [optional]
   * @param valueBlockType Type of value block
   */
  constructor(parameters = {}, valueBlockType = ValueBlock) {
    super(parameters);
    if ("name" in parameters) this.name = parameters.name;
    if ("optional" in parameters) this.optional = parameters.optional;
    if ("primitiveSchema" in parameters) this.primitiveSchema = parameters.primitiveSchema;
    this.idBlock = new LocalIdentificationBlock(parameters);
    this.lenBlock = new LocalLengthBlock(parameters);
    this.valueBlock = new valueBlockType(parameters);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "BaseBlock";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    let retBuf;
    const idBlockBuf = this.idBlock.toBER(sizeOnly);
    const valueBlockSizeBuf = this.valueBlock.toBER(true);
    this.lenBlock.length = valueBlockSizeBuf.byteLength;
    const lenBlockBuf = this.lenBlock.toBER(sizeOnly);
    retBuf = (0, _pvutils.utilConcatBuf)(idBlockBuf, lenBlockBuf);
    let valueBlockBuf;
    if (sizeOnly === false) valueBlockBuf = this.valueBlock.toBER(sizeOnly);else valueBlockBuf = new ArrayBuffer(this.lenBlock.length);
    retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBlockBuf);

    if (this.lenBlock.isIndefiniteForm === true) {
      const indefBuf = new ArrayBuffer(2);

      if (sizeOnly === false) {
        const indefView = new Uint8Array(indefBuf);
        indefView[0] = 0x00;
        indefView[1] = 0x00;
      }

      retBuf = (0, _pvutils.utilConcatBuf)(retBuf, indefBuf);
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.idBlock = this.idBlock.toJSON();
    object.lenBlock = this.lenBlock.toJSON();
    object.valueBlock = this.valueBlock.toJSON();
    if ("name" in this) object.name = this.name;
    if ("optional" in this) object.optional = this.optional;
    if ("primitiveSchema" in this) object.primitiveSchema = this.primitiveSchema.toJSON();
    return object;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of basic block for all PRIMITIVE types
//**************************************************************************************


exports.BaseBlock = BaseBlock;

class LocalPrimitiveValueBlock extends ValueBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalPrimitiveValueBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueBeforeDecode]
   */
  constructor(parameters = {}) {
    super(parameters); //region Variables from "hexBlock" class

    if ("valueHex" in parameters) this.valueHex = parameters.valueHex.slice(0);else this.valueHex = new ArrayBuffer(0);
    this.isHexOnly = (0, _pvutils.getParametersValue)(parameters, "isHexOnly", true); //endregion
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures
    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion
    //region Getting Uint8Array from ArrayBuffer

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion
    //region Initial checks

    if (intBuffer.length === 0) {
      this.warnings.push("Zero buffer length");
      return inputOffset;
    } //endregion
    //region Copy input buffer into internal buffer


    this.valueHex = new ArrayBuffer(intBuffer.length);
    const valueHexView = new Uint8Array(this.valueHex);

    for (let i = 0; i < intBuffer.length; i++) valueHexView[i] = intBuffer[i]; //endregion


    this.blockLength = inputLength;
    return inputOffset + inputLength;
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    return this.valueHex.slice(0);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "PrimitiveValueBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);
    object.isHexOnly = this.isHexOnly;
    return object;
  } //**********************************************************************************


} //**************************************************************************************


class Primitive extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "Primitive" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters, LocalPrimitiveValueBlock);
    this.idBlock.isConstructed = false;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "PRIMITIVE";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of basic block for all CONSTRUCTED types
//**************************************************************************************


exports.Primitive = Primitive;

class LocalConstructedValueBlock extends ValueBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalConstructedValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.value = (0, _pvutils.getParametersValue)(parameters, "value", []);
    this.isIndefiniteForm = (0, _pvutils.getParametersValue)(parameters, "isIndefiniteForm", false);
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Store initial offset and length
    const initialOffset = inputOffset;
    const initialLength = inputLength; //endregion
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures

    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion
    //region Getting Uint8Array from ArrayBuffer

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion
    //region Initial checks

    if (intBuffer.length === 0) {
      this.warnings.push("Zero buffer length");
      return inputOffset;
    } //endregion
    //region Aux function


    function checkLen(indefiniteLength, length) {
      if (indefiniteLength === true) return 1;
      return length;
    } //endregion


    let currentOffset = inputOffset;

    while (checkLen(this.isIndefiniteForm, inputLength) > 0) {
      const returnObject = LocalFromBER(inputBuffer, currentOffset, inputLength);

      if (returnObject.offset === -1) {
        this.error = returnObject.result.error;
        this.warnings.concat(returnObject.result.warnings);
        return -1;
      }

      currentOffset = returnObject.offset;
      this.blockLength += returnObject.result.blockLength;
      inputLength -= returnObject.result.blockLength;
      this.value.push(returnObject.result);
      if (this.isIndefiniteForm === true && returnObject.result.constructor.blockName() === EndOfContent.blockName()) break;
    }

    if (this.isIndefiniteForm === true) {
      if (this.value[this.value.length - 1].constructor.blockName() === EndOfContent.blockName()) this.value.pop();else this.warnings.push("No EndOfContent block encoded");
    } //region Copy "inputBuffer" to "valueBeforeDecode"


    this.valueBeforeDecode = inputBuffer.slice(initialOffset, initialOffset + initialLength); //endregion

    return currentOffset;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    let retBuf = new ArrayBuffer(0);

    for (let i = 0; i < this.value.length; i++) {
      const valueBuf = this.value[i].toBER(sizeOnly);
      retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBuf);
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "ConstructedValueBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.isIndefiniteForm = this.isIndefiniteForm;
    object.value = [];

    for (let i = 0; i < this.value.length; i++) object.value.push(this.value[i].toJSON());

    return object;
  } //**********************************************************************************


} //**************************************************************************************


class Constructed extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "Constructed" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalConstructedValueBlock);
    this.idBlock.isConstructed = true;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "CONSTRUCTED";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 EndOfContent type class
//**************************************************************************************


exports.Constructed = Constructed;

class LocalEndOfContentValueBlock extends ValueBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalEndOfContentValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols,JSUnusedLocalSymbols

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number}
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region There is no "value block" for EndOfContent type and we need to return the same offset
    return inputOffset; //endregion
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    return new ArrayBuffer(0);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "EndOfContentValueBlock";
  } //**********************************************************************************


} //**************************************************************************************


class EndOfContent extends BaseBlock {
  //**********************************************************************************
  constructor(paramaters = {}) {
    super(paramaters, LocalEndOfContentValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 0; // EndOfContent
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "EndOfContent";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Boolean type class
//**************************************************************************************


exports.EndOfContent = EndOfContent;

class LocalBooleanValueBlock extends ValueBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalBooleanValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.value = (0, _pvutils.getParametersValue)(parameters, "value", false);
    this.isHexOnly = (0, _pvutils.getParametersValue)(parameters, "isHexOnly", false);
    if ("valueHex" in parameters) this.valueHex = parameters.valueHex.slice(0);else {
      this.valueHex = new ArrayBuffer(1);

      if (this.value === true) {
        const view = new Uint8Array(this.valueHex);
        view[0] = 0xFF;
      }
    }
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures
    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion
    //region Getting Uint8Array from ArrayBuffer

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion

    if (inputLength > 1) this.warnings.push("Boolean value encoded in more then 1 octet");
    this.isHexOnly = true; //region Copy input buffer to internal array

    this.valueHex = new ArrayBuffer(intBuffer.length);
    const view = new Uint8Array(this.valueHex);

    for (let i = 0; i < intBuffer.length; i++) view[i] = intBuffer[i]; //endregion


    if (_pvutils.utilDecodeTC.call(this) !== 0) this.value = true;else this.value = false;
    this.blockLength = inputLength;
    return inputOffset + inputLength;
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    return this.valueHex;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "BooleanValueBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.value;
    object.isHexOnly = this.isHexOnly;
    object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);
    return object;
  } //**********************************************************************************


} //**************************************************************************************


class Boolean extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "Boolean" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalBooleanValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 1; // Boolean
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Boolean";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Sequence and Set type classes
//**************************************************************************************


exports.Boolean = Boolean;

class Sequence extends Constructed {
  //**********************************************************************************

  /**
   * Constructor for "Sequence" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 16; // Sequence
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Sequence";
  } //**********************************************************************************


} //**************************************************************************************


exports.Sequence = Sequence;

class Set extends Constructed {
  //**********************************************************************************

  /**
   * Constructor for "Set" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 17; // Set
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Set";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Null type class
//**************************************************************************************


exports.Set = Set;

class Null extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "Null" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalBaseBlock); // We will not have a call to "Null value block" because of specified "fromBER" and "toBER" functions

    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 5; // Null
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Null";
  } //**********************************************************************************
  //noinspection JSUnusedLocalSymbols

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    if (this.lenBlock.length > 0) this.warnings.push("Non-zero length of value block for Null type");
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    this.blockLength += inputLength;

    if (inputOffset + inputLength > inputBuffer.byteLength) {
      this.error = "End of input reached before message was fully decoded (inconsistent offset and length values)";
      return -1;
    }

    return inputOffset + inputLength;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    const retBuf = new ArrayBuffer(2);
    if (sizeOnly === true) return retBuf;
    const retView = new Uint8Array(retBuf);
    retView[0] = 0x05;
    retView[1] = 0x00;
    return retBuf;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 OctetString type class
//**************************************************************************************


exports.Null = Null;

class LocalOctetStringValueBlock extends HexBlock(LocalConstructedValueBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalOctetStringValueBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.isConstructed = (0, _pvutils.getParametersValue)(parameters, "isConstructed", false);
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    let resultOffset = 0;

    if (this.isConstructed === true) {
      this.isHexOnly = false;
      resultOffset = LocalConstructedValueBlock.prototype.fromBER.call(this, inputBuffer, inputOffset, inputLength);
      if (resultOffset === -1) return resultOffset;

      for (let i = 0; i < this.value.length; i++) {
        const currentBlockName = this.value[i].constructor.blockName();

        if (currentBlockName === EndOfContent.blockName()) {
          if (this.isIndefiniteForm === true) break;else {
            this.error = "EndOfContent is unexpected, OCTET STRING may consists of OCTET STRINGs only";
            return -1;
          }
        }

        if (currentBlockName !== OctetString.blockName()) {
          this.error = "OCTET STRING may consists of OCTET STRINGs only";
          return -1;
        }
      }
    } else {
      this.isHexOnly = true;
      resultOffset = super.fromBER(inputBuffer, inputOffset, inputLength);
      this.blockLength = inputLength;
    }

    return resultOffset;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    if (this.isConstructed === true) return LocalConstructedValueBlock.prototype.toBER.call(this, sizeOnly);
    let retBuf = new ArrayBuffer(this.valueHex.byteLength);
    if (sizeOnly === true) return retBuf;
    if (this.valueHex.byteLength === 0) return retBuf;
    retBuf = this.valueHex.slice(0);
    return retBuf;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "OctetStringValueBlock";
  } //**********************************************************************************


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.isConstructed = this.isConstructed;
    object.isHexOnly = this.isHexOnly;
    object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);
    return object;
  } //**********************************************************************************


} //**************************************************************************************


class OctetString extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "OctetString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalOctetStringValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 4; // OctetString
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    this.valueBlock.isConstructed = this.idBlock.isConstructed;
    this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm; //region Ability to encode empty OCTET STRING

    if (inputLength === 0) {
      if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
      if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
      return inputOffset;
    } //endregion


    return super.fromBER(inputBuffer, inputOffset, inputLength);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "OctetString";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Checking that two OCTETSTRINGs are equal
   * @param {OctetString} octetString
   */


  isEqual(octetString) {
    //region Check input type
    if (octetString instanceof OctetString === false) return false; //endregion
    //region Compare two JSON strings

    if (JSON.stringify(this) !== JSON.stringify(octetString)) return false; //endregion

    return true;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 BitString type class
//**************************************************************************************


exports.OctetString = OctetString;

class LocalBitStringValueBlock extends HexBlock(LocalConstructedValueBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalBitStringValueBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.unusedBits = (0, _pvutils.getParametersValue)(parameters, "unusedBits", 0);
    this.isConstructed = (0, _pvutils.getParametersValue)(parameters, "isConstructed", false);
    this.blockLength = this.valueHex.byteLength;
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Ability to decode zero-length BitString value
    if (inputLength === 0) return inputOffset; //endregion

    let resultOffset = -1; //region If the BISTRING supposed to be a constructed value

    if (this.isConstructed === true) {
      resultOffset = LocalConstructedValueBlock.prototype.fromBER.call(this, inputBuffer, inputOffset, inputLength);
      if (resultOffset === -1) return resultOffset;

      for (let i = 0; i < this.value.length; i++) {
        const currentBlockName = this.value[i].constructor.blockName();

        if (currentBlockName === EndOfContent.blockName()) {
          if (this.isIndefiniteForm === true) break;else {
            this.error = "EndOfContent is unexpected, BIT STRING may consists of BIT STRINGs only";
            return -1;
          }
        }

        if (currentBlockName !== BitString.blockName()) {
          this.error = "BIT STRING may consists of BIT STRINGs only";
          return -1;
        }

        if (this.unusedBits > 0 && this.value[i].valueBlock.unusedBits > 0) {
          this.error = "Usign of \"unused bits\" inside constructive BIT STRING allowed for least one only";
          return -1;
        }

        this.unusedBits = this.value[i].valueBlock.unusedBits;

        if (this.unusedBits > 7) {
          this.error = "Unused bits for BitString must be in range 0-7";
          return -1;
        }
      }

      return resultOffset;
    } //endregion
    //region If the BitString supposed to be a primitive value
    //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures


    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength);
    this.unusedBits = intBuffer[0];

    if (this.unusedBits > 7) {
      this.error = "Unused bits for BitString must be in range 0-7";
      return -1;
    } //region Copy input buffer to internal buffer


    this.valueHex = new ArrayBuffer(intBuffer.length - 1);
    const view = new Uint8Array(this.valueHex);

    for (let i = 0; i < inputLength - 1; i++) view[i] = intBuffer[i + 1]; //endregion


    this.blockLength = intBuffer.length;
    return inputOffset + inputLength; //endregion
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    if (this.isConstructed === true) return LocalConstructedValueBlock.prototype.toBER.call(this, sizeOnly);
    if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength + 1);
    if (this.valueHex.byteLength === 0) return new ArrayBuffer(0);
    const curView = new Uint8Array(this.valueHex);
    const retBuf = new ArrayBuffer(this.valueHex.byteLength + 1);
    const retView = new Uint8Array(retBuf);
    retView[0] = this.unusedBits;

    for (let i = 0; i < this.valueHex.byteLength; i++) retView[i + 1] = curView[i];

    return retBuf;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "BitStringValueBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.unusedBits = this.unusedBits;
    object.isConstructed = this.isConstructed;
    object.isHexOnly = this.isHexOnly;
    object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);
    return object;
  } //**********************************************************************************


} //**************************************************************************************


class BitString extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "BitString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalBitStringValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 3; // BitString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "BitString";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    //region Ability to encode empty BitString
    if (inputLength === 0) return inputOffset; //endregion

    this.valueBlock.isConstructed = this.idBlock.isConstructed;
    this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
    return super.fromBER(inputBuffer, inputOffset, inputLength);
  } //**********************************************************************************

  /**
   * Checking that two BITSTRINGs are equal
   * @param {BitString} bitString
   */


  isEqual(bitString) {
    //region Check input type
    if (bitString instanceof BitString === false) return false; //endregion
    //region Compare two JSON strings

    if (JSON.stringify(this) !== JSON.stringify(bitString)) return false; //endregion

    return true;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Integer type class
//**************************************************************************************

/**
 * @extends ValueBlock
 */


exports.BitString = BitString;

class LocalIntegerValueBlock extends HexBlock(ValueBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalIntegerValueBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters);
    if ("value" in parameters) this.valueDec = parameters.value;
  } //**********************************************************************************

  /**
   * Setter for "valueHex"
   * @param {ArrayBuffer} _value
   */


  set valueHex(_value) {
    this._valueHex = _value.slice(0);

    if (_value.byteLength >= 4) {
      this.warnings.push("Too big Integer for decoding, hex only");
      this.isHexOnly = true;
      this._valueDec = 0;
    } else {
      this.isHexOnly = false;
      if (_value.byteLength > 0) this._valueDec = _pvutils.utilDecodeTC.call(this);
    }
  } //**********************************************************************************

  /**
   * Getter for "valueHex"
   * @returns {ArrayBuffer}
   */


  get valueHex() {
    return this._valueHex;
  } //**********************************************************************************

  /**
   * Getter for "valueDec"
   * @param {number} _value
   */


  set valueDec(_value) {
    this._valueDec = _value;
    this.isHexOnly = false;
    this._valueHex = (0, _pvutils.utilEncodeTC)(_value);
  } //**********************************************************************************

  /**
   * Getter for "valueDec"
   * @returns {number}
   */


  get valueDec() {
    return this._valueDec;
  } //**********************************************************************************

  /**
   * Base function for converting block from DER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 DER encoded array
   * @param {!number} inputOffset Offset in ASN.1 DER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @param {number} [expectedLength=0] Expected length of converted "valueHex" buffer
   * @returns {number} Offset after least decoded byte
   */


  fromDER(inputBuffer, inputOffset, inputLength, expectedLength = 0) {
    const offset = this.fromBER(inputBuffer, inputOffset, inputLength);
    if (offset === -1) return offset;
    const view = new Uint8Array(this._valueHex);

    if (view[0] === 0x00 && (view[1] & 0x80) !== 0) {
      const updatedValueHex = new ArrayBuffer(this._valueHex.byteLength - 1);
      const updatedView = new Uint8Array(updatedValueHex);
      updatedView.set(new Uint8Array(this._valueHex, 1, this._valueHex.byteLength - 1));
      this._valueHex = updatedValueHex.slice(0);
    } else {
      if (expectedLength !== 0) {
        if (this._valueHex.byteLength < expectedLength) {
          if (expectedLength - this._valueHex.byteLength > 1) expectedLength = this._valueHex.byteLength + 1;
          const updatedValueHex = new ArrayBuffer(expectedLength);
          const updatedView = new Uint8Array(updatedValueHex);
          updatedView.set(view, expectedLength - this._valueHex.byteLength);
          this._valueHex = updatedValueHex.slice(0);
        }
      }
    }

    return offset;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (DER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toDER(sizeOnly = false) {
    const view = new Uint8Array(this._valueHex);

    switch (true) {
      case (view[0] & 0x80) !== 0:
        {
          const updatedValueHex = new ArrayBuffer(this._valueHex.byteLength + 1);
          const updatedView = new Uint8Array(updatedValueHex);
          updatedView[0] = 0x00;
          updatedView.set(view, 1);
          this._valueHex = updatedValueHex.slice(0);
        }
        break;

      case view[0] === 0x00 && (view[1] & 0x80) === 0:
        {
          const updatedValueHex = new ArrayBuffer(this._valueHex.byteLength - 1);
          const updatedView = new Uint8Array(updatedValueHex);
          updatedView.set(new Uint8Array(this._valueHex, 1, this._valueHex.byteLength - 1));
          this._valueHex = updatedValueHex.slice(0);
        }
        break;

      default:
    }

    return this.toBER(sizeOnly);
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = super.fromBER(inputBuffer, inputOffset, inputLength);
    if (resultOffset === -1) return resultOffset;
    this.blockLength = inputLength;
    return inputOffset + inputLength;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    //noinspection JSCheckFunctionSignatures
    return this.valueHex.slice(0);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "IntegerValueBlock";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.valueDec = this.valueDec;
    return object;
  } //**********************************************************************************

  /**
   * Convert current value to decimal string representation
   */


  toString() {
    //region Aux functions
    function viewAdd(first, second) {
      //region Initial variables
      const c = new Uint8Array([0]);
      let firstView = new Uint8Array(first);
      let secondView = new Uint8Array(second);
      let firstViewCopy = firstView.slice(0);
      const firstViewCopyLength = firstViewCopy.length - 1;
      let secondViewCopy = secondView.slice(0);
      const secondViewCopyLength = secondViewCopy.length - 1;
      let value = 0;
      const max = secondViewCopyLength < firstViewCopyLength ? firstViewCopyLength : secondViewCopyLength;
      let counter = 0; //endregion

      for (let i = max; i >= 0; i--, counter++) {
        switch (true) {
          case counter < secondViewCopy.length:
            value = firstViewCopy[firstViewCopyLength - counter] + secondViewCopy[secondViewCopyLength - counter] + c[0];
            break;

          default:
            value = firstViewCopy[firstViewCopyLength - counter] + c[0];
        }

        c[0] = value / 10;

        switch (true) {
          case counter >= firstViewCopy.length:
            firstViewCopy = (0, _pvutils.utilConcatView)(new Uint8Array([value % 10]), firstViewCopy);
            break;

          default:
            firstViewCopy[firstViewCopyLength - counter] = value % 10;
        }
      }

      if (c[0] > 0) firstViewCopy = (0, _pvutils.utilConcatView)(c, firstViewCopy);
      return firstViewCopy.slice(0);
    }

    function power2(n) {
      if (n >= powers2.length) {
        for (let p = powers2.length; p <= n; p++) {
          const c = new Uint8Array([0]);
          let digits = powers2[p - 1].slice(0);

          for (let i = digits.length - 1; i >= 0; i--) {
            const newValue = new Uint8Array([(digits[i] << 1) + c[0]]);
            c[0] = newValue[0] / 10;
            digits[i] = newValue[0] % 10;
          }

          if (c[0] > 0) digits = (0, _pvutils.utilConcatView)(c, digits);
          powers2.push(digits);
        }
      }

      return powers2[n];
    }

    function viewSub(first, second) {
      //region Initial variables
      let b = 0;
      let firstView = new Uint8Array(first);
      let secondView = new Uint8Array(second);
      let firstViewCopy = firstView.slice(0);
      const firstViewCopyLength = firstViewCopy.length - 1;
      let secondViewCopy = secondView.slice(0);
      const secondViewCopyLength = secondViewCopy.length - 1;
      let value;
      let counter = 0; //endregion

      for (let i = secondViewCopyLength; i >= 0; i--, counter++) {
        value = firstViewCopy[firstViewCopyLength - counter] - secondViewCopy[secondViewCopyLength - counter] - b;

        switch (true) {
          case value < 0:
            b = 1;
            firstViewCopy[firstViewCopyLength - counter] = value + 10;
            break;

          default:
            b = 0;
            firstViewCopy[firstViewCopyLength - counter] = value;
        }
      }

      if (b > 0) {
        for (let i = firstViewCopyLength - secondViewCopyLength + 1; i >= 0; i--, counter++) {
          value = firstViewCopy[firstViewCopyLength - counter] - b;

          if (value < 0) {
            b = 1;
            firstViewCopy[firstViewCopyLength - counter] = value + 10;
          } else {
            b = 0;
            firstViewCopy[firstViewCopyLength - counter] = value;
            break;
          }
        }
      }

      return firstViewCopy.slice();
    } //endregion
    //region Initial variables


    const firstBit = this._valueHex.byteLength * 8 - 1;
    let digits = new Uint8Array(this._valueHex.byteLength * 8 / 3);
    let bitNumber = 0;
    let currentByte;
    const asn1View = new Uint8Array(this._valueHex);
    let result = "";
    let flag = false; //endregion
    //region Calculate number

    for (let byteNumber = this._valueHex.byteLength - 1; byteNumber >= 0; byteNumber--) {
      currentByte = asn1View[byteNumber];

      for (let i = 0; i < 8; i++) {
        if ((currentByte & 1) === 1) {
          switch (bitNumber) {
            case firstBit:
              digits = viewSub(power2(bitNumber), digits);
              result = "-";
              break;

            default:
              digits = viewAdd(digits, power2(bitNumber));
          }
        }

        bitNumber++;
        currentByte >>= 1;
      }
    } //endregion
    //region Print number


    for (let i = 0; i < digits.length; i++) {
      if (digits[i]) flag = true;
      if (flag) result += digitsString.charAt(digits[i]);
    }

    if (flag === false) result += digitsString.charAt(0); //endregion

    return result;
  } //**********************************************************************************


} //**************************************************************************************


class Integer extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "Integer" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalIntegerValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 2; // Integer
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Integer";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Compare two Integer object, or Integer and ArrayBuffer objects
   * @param {!Integer|ArrayBuffer} otherValue
   * @returns {boolean}
   */


  isEqual(otherValue) {
    if (otherValue instanceof Integer) {
      if (this.valueBlock.isHexOnly && otherValue.valueBlock.isHexOnly) // Compare two ArrayBuffers
        return (0, _pvutils.isEqualBuffer)(this.valueBlock.valueHex, otherValue.valueBlock.valueHex);
      if (this.valueBlock.isHexOnly === otherValue.valueBlock.isHexOnly) return this.valueBlock.valueDec === otherValue.valueBlock.valueDec;
      return false;
    }

    if (otherValue instanceof ArrayBuffer) return (0, _pvutils.isEqualBuffer)(this.valueBlock.valueHex, otherValue);
    return false;
  } //**********************************************************************************

  /**
   * Convert current Integer value from BER into DER format
   * @returns {Integer}
   */


  convertToDER() {
    const integer = new Integer({
      valueHex: this.valueBlock.valueHex
    });
    integer.valueBlock.toDER();
    return integer;
  } //**********************************************************************************

  /**
   * Convert current Integer value from DER to BER format
   * @returns {Integer}
   */


  convertFromDER() {
    const expectedLength = this.valueBlock.valueHex.byteLength % 2 ? this.valueBlock.valueHex.byteLength + 1 : this.valueBlock.valueHex.byteLength;
    const integer = new Integer({
      valueHex: this.valueBlock.valueHex
    });
    integer.valueBlock.fromDER(integer.valueBlock.valueHex, 0, integer.valueBlock.valueHex.byteLength, expectedLength);
    return integer;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Enumerated type class
//**************************************************************************************


exports.Integer = Integer;

class Enumerated extends Integer {
  //**********************************************************************************

  /**
   * Constructor for "Enumerated" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 10; // Enumerated
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Enumerated";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 ObjectIdentifier type class
//**************************************************************************************


exports.Enumerated = Enumerated;

class LocalSidValueBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalSidValueBlock" class
   * @param {Object} [parameters={}]
   * @property {number} [valueDec]
   * @property {boolean} [isFirstSid]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.valueDec = (0, _pvutils.getParametersValue)(parameters, "valueDec", -1);
    this.isFirstSid = (0, _pvutils.getParametersValue)(parameters, "isFirstSid", false);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "sidBlock";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    if (inputLength === 0) return inputOffset; //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures

    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength);
    this.valueHex = new ArrayBuffer(inputLength);
    let view = new Uint8Array(this.valueHex);

    for (let i = 0; i < inputLength; i++) {
      view[i] = intBuffer[i] & 0x7F;
      this.blockLength++;
      if ((intBuffer[i] & 0x80) === 0x00) break;
    } //region Ajust size of valueHex buffer


    const tempValueHex = new ArrayBuffer(this.blockLength);
    const tempView = new Uint8Array(tempValueHex);

    for (let i = 0; i < this.blockLength; i++) tempView[i] = view[i]; //noinspection JSCheckFunctionSignatures


    this.valueHex = tempValueHex.slice(0);
    view = new Uint8Array(this.valueHex); //endregion

    if ((intBuffer[this.blockLength - 1] & 0x80) !== 0x00) {
      this.error = "End of input reached before message was fully decoded";
      return -1;
    }

    if (view[0] === 0x00) this.warnings.push("Needlessly long format of SID encoding");
    if (this.blockLength <= 8) this.valueDec = (0, _pvutils.utilFromBase)(view, 7);else {
      this.isHexOnly = true;
      this.warnings.push("Too big SID for decoding, hex only");
    }
    return inputOffset + this.blockLength;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    //region Initial variables
    let retBuf;
    let retView; //endregion

    if (this.isHexOnly) {
      if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength);
      const curView = new Uint8Array(this.valueHex);
      retBuf = new ArrayBuffer(this.blockLength);
      retView = new Uint8Array(retBuf);

      for (let i = 0; i < this.blockLength - 1; i++) retView[i] = curView[i] | 0x80;

      retView[this.blockLength - 1] = curView[this.blockLength - 1];
      return retBuf;
    }

    const encodedBuf = (0, _pvutils.utilToBase)(this.valueDec, 7);

    if (encodedBuf.byteLength === 0) {
      this.error = "Error during encoding SID value";
      return new ArrayBuffer(0);
    }

    retBuf = new ArrayBuffer(encodedBuf.byteLength);

    if (sizeOnly === false) {
      const encodedView = new Uint8Array(encodedBuf);
      retView = new Uint8Array(retBuf);

      for (let i = 0; i < encodedBuf.byteLength - 1; i++) retView[i] = encodedView[i] | 0x80;

      retView[encodedBuf.byteLength - 1] = encodedView[encodedBuf.byteLength - 1];
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Create string representation of current SID block
   * @returns {string}
   */


  toString() {
    let result = "";
    if (this.isHexOnly === true) result = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);else {
      if (this.isFirstSid) {
        let sidValue = this.valueDec;
        if (this.valueDec <= 39) result = "0.";else {
          if (this.valueDec <= 79) {
            result = "1.";
            sidValue -= 40;
          } else {
            result = "2.";
            sidValue -= 80;
          }
        }
        result += sidValue.toString();
      } else result = this.valueDec.toString();
    }
    return result;
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.valueDec = this.valueDec;
    object.isFirstSid = this.isFirstSid;
    return object;
  } //**********************************************************************************


} //**************************************************************************************


class LocalObjectIdentifierValueBlock extends ValueBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalObjectIdentifierValueBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.fromString((0, _pvutils.getParametersValue)(parameters, "value", ""));
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    let resultOffset = inputOffset;

    while (inputLength > 0) {
      const sidBlock = new LocalSidValueBlock();
      resultOffset = sidBlock.fromBER(inputBuffer, resultOffset, inputLength);

      if (resultOffset === -1) {
        this.blockLength = 0;
        this.error = sidBlock.error;
        return resultOffset;
      }

      if (this.value.length === 0) sidBlock.isFirstSid = true;
      this.blockLength += sidBlock.blockLength;
      inputLength -= sidBlock.blockLength;
      this.value.push(sidBlock);
    }

    return resultOffset;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    let retBuf = new ArrayBuffer(0);

    for (let i = 0; i < this.value.length; i++) {
      const valueBuf = this.value[i].toBER(sizeOnly);

      if (valueBuf.byteLength === 0) {
        this.error = this.value[i].error;
        return new ArrayBuffer(0);
      }

      retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBuf);
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Create "LocalObjectIdentifierValueBlock" class from string
   * @param {string} string Input string to convert from
   * @returns {boolean}
   */


  fromString(string) {
    this.value = []; // Clear existing SID values

    let pos1 = 0;
    let pos2 = 0;
    let sid = "";
    let flag = false;

    do {
      pos2 = string.indexOf(".", pos1);
      if (pos2 === -1) sid = string.substr(pos1);else sid = string.substr(pos1, pos2 - pos1);
      pos1 = pos2 + 1;

      if (flag) {
        const sidBlock = this.value[0];
        let plus = 0;

        switch (sidBlock.valueDec) {
          case 0:
            break;

          case 1:
            plus = 40;
            break;

          case 2:
            plus = 80;
            break;

          default:
            this.value = []; // clear SID array

            return false;
          // ???
        }

        const parsedSID = parseInt(sid, 10);
        if (isNaN(parsedSID)) return true;
        sidBlock.valueDec = parsedSID + plus;
        flag = false;
      } else {
        const sidBlock = new LocalSidValueBlock();
        sidBlock.valueDec = parseInt(sid, 10);
        if (isNaN(sidBlock.valueDec)) return true;

        if (this.value.length === 0) {
          sidBlock.isFirstSid = true;
          flag = true;
        }

        this.value.push(sidBlock);
      }
    } while (pos2 !== -1);

    return true;
  } //**********************************************************************************

  /**
   * Converts "LocalObjectIdentifierValueBlock" class to string
   * @returns {string}
   */


  toString() {
    let result = "";
    let isHexOnly = false;

    for (let i = 0; i < this.value.length; i++) {
      isHexOnly = this.value[i].isHexOnly;
      let sidStr = this.value[i].toString();
      if (i !== 0) result = `${result}.`;

      if (isHexOnly) {
        sidStr = `{${sidStr}}`;
        if (this.value[i].isFirstSid) result = `2.{${sidStr} - 80}`;else result += sidStr;
      } else result += sidStr;
    }

    return result;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "ObjectIdentifierValueBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.toString();
    object.sidArray = [];

    for (let i = 0; i < this.value.length; i++) object.sidArray.push(this.value[i].toJSON());

    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends BaseBlock
 */


class ObjectIdentifier extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "ObjectIdentifier" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters, LocalObjectIdentifierValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 6; // OBJECT IDENTIFIER
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "ObjectIdentifier";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of all string's classes
//**************************************************************************************


exports.ObjectIdentifier = ObjectIdentifier;

class LocalUtf8StringValueBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Constructor for "LocalUtf8StringValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.isHexOnly = true;
    this.value = ""; // String representation of decoded ArrayBuffer
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Utf8StringValueBlock";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.value;
    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends BaseBlock
 */


class Utf8String extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "Utf8String" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters, LocalUtf8StringValueBlock);
    if ("value" in parameters) this.fromString(parameters.value);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 12; // Utf8String
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Utf8String";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    this.fromBuffer(this.valueBlock.valueHex);
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Function converting ArrayBuffer into ASN.1 internal string
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   */


  fromBuffer(inputBuffer) {
    this.valueBlock.value = String.fromCharCode.apply(null, new Uint8Array(inputBuffer));

    try {
      //noinspection JSDeprecatedSymbols
      this.valueBlock.value = decodeURIComponent(escape(this.valueBlock.value));
    } catch (ex) {
      this.warnings.push(`Error during "decodeURIComponent": ${ex}, using raw string`);
    }
  } //**********************************************************************************

  /**
   * Function converting JavaScript string into ASN.1 internal class
   * @param {!string} inputString ASN.1 BER encoded array
   */


  fromString(inputString) {
    //noinspection JSDeprecatedSymbols
    const str = unescape(encodeURIComponent(inputString));
    const strLen = str.length;
    this.valueBlock.valueHex = new ArrayBuffer(strLen);
    const view = new Uint8Array(this.valueBlock.valueHex);

    for (let i = 0; i < strLen; i++) view[i] = str.charCodeAt(i);

    this.valueBlock.value = inputString;
  } //**********************************************************************************


} //**************************************************************************************
//region Declaration of ASN.1 RelativeObjectIdentifier type class
//**************************************************************************************


exports.Utf8String = Utf8String;

class LocalRelativeSidValueBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalRelativeSidValueBlock" class
   * @param {Object} [parameters={}]
   * @property {number} [valueDec]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.valueDec = (0, _pvutils.getParametersValue)(parameters, "valueDec", -1);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "relativeSidBlock";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    if (inputLength === 0) return inputOffset; //region Basic check for parameters
    //noinspection JSCheckFunctionSignatures

    if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion

    const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength);
    this.valueHex = new ArrayBuffer(inputLength);
    let view = new Uint8Array(this.valueHex);

    for (let i = 0; i < inputLength; i++) {
      view[i] = intBuffer[i] & 0x7F;
      this.blockLength++;
      if ((intBuffer[i] & 0x80) === 0x00) break;
    } //region Ajust size of valueHex buffer


    const tempValueHex = new ArrayBuffer(this.blockLength);
    const tempView = new Uint8Array(tempValueHex);

    for (let i = 0; i < this.blockLength; i++) tempView[i] = view[i]; //noinspection JSCheckFunctionSignatures


    this.valueHex = tempValueHex.slice(0);
    view = new Uint8Array(this.valueHex); //endregion

    if ((intBuffer[this.blockLength - 1] & 0x80) !== 0x00) {
      this.error = "End of input reached before message was fully decoded";
      return -1;
    }

    if (view[0] === 0x00) this.warnings.push("Needlessly long format of SID encoding");
    if (this.blockLength <= 8) this.valueDec = (0, _pvutils.utilFromBase)(view, 7);else {
      this.isHexOnly = true;
      this.warnings.push("Too big SID for decoding, hex only");
    }
    return inputOffset + this.blockLength;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    //region Initial variables
    let retBuf;
    let retView; //endregion

    if (this.isHexOnly) {
      if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength);
      const curView = new Uint8Array(this.valueHex);
      retBuf = new ArrayBuffer(this.blockLength);
      retView = new Uint8Array(retBuf);

      for (let i = 0; i < this.blockLength - 1; i++) retView[i] = curView[i] | 0x80;

      retView[this.blockLength - 1] = curView[this.blockLength - 1];
      return retBuf;
    }

    const encodedBuf = (0, _pvutils.utilToBase)(this.valueDec, 7);

    if (encodedBuf.byteLength === 0) {
      this.error = "Error during encoding SID value";
      return new ArrayBuffer(0);
    }

    retBuf = new ArrayBuffer(encodedBuf.byteLength);

    if (sizeOnly === false) {
      const encodedView = new Uint8Array(encodedBuf);
      retView = new Uint8Array(retBuf);

      for (let i = 0; i < encodedBuf.byteLength - 1; i++) retView[i] = encodedView[i] | 0x80;

      retView[encodedBuf.byteLength - 1] = encodedView[encodedBuf.byteLength - 1];
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Create string representation of current SID block
   * @returns {string}
   */


  toString() {
    let result = "";
    if (this.isHexOnly === true) result = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);else {
      result = this.valueDec.toString();
    }
    return result;
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.valueDec = this.valueDec;
    return object;
  } //**********************************************************************************


} //**************************************************************************************


class LocalRelativeObjectIdentifierValueBlock extends ValueBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalRelativeObjectIdentifierValueBlock" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.fromString((0, _pvutils.getParametersValue)(parameters, "value", ""));
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    let resultOffset = inputOffset;

    while (inputLength > 0) {
      const sidBlock = new LocalRelativeSidValueBlock();
      resultOffset = sidBlock.fromBER(inputBuffer, resultOffset, inputLength);

      if (resultOffset === -1) {
        this.blockLength = 0;
        this.error = sidBlock.error;
        return resultOffset;
      }

      this.blockLength += sidBlock.blockLength;
      inputLength -= sidBlock.blockLength;
      this.value.push(sidBlock);
    }

    return resultOffset;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    let retBuf = new ArrayBuffer(0);

    for (let i = 0; i < this.value.length; i++) {
      const valueBuf = this.value[i].toBER(sizeOnly);

      if (valueBuf.byteLength === 0) {
        this.error = this.value[i].error;
        return new ArrayBuffer(0);
      }

      retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBuf);
    }

    return retBuf;
  } //**********************************************************************************

  /**
   * Create "LocalRelativeObjectIdentifierValueBlock" class from string
   * @param {string} string Input string to convert from
   * @returns {boolean}
   */


  fromString(string) {
    this.value = []; // Clear existing SID values

    let pos1 = 0;
    let pos2 = 0;
    let sid = "";

    do {
      pos2 = string.indexOf(".", pos1);
      if (pos2 === -1) sid = string.substr(pos1);else sid = string.substr(pos1, pos2 - pos1);
      pos1 = pos2 + 1;
      const sidBlock = new LocalRelativeSidValueBlock();
      sidBlock.valueDec = parseInt(sid, 10);
      if (isNaN(sidBlock.valueDec)) return true;
      this.value.push(sidBlock);
    } while (pos2 !== -1);

    return true;
  } //**********************************************************************************

  /**
   * Converts "LocalRelativeObjectIdentifierValueBlock" class to string
   * @returns {string}
   */


  toString() {
    let result = "";
    let isHexOnly = false;

    for (let i = 0; i < this.value.length; i++) {
      isHexOnly = this.value[i].isHexOnly;
      let sidStr = this.value[i].toString();
      if (i !== 0) result = `${result}.`;

      if (isHexOnly) {
        sidStr = `{${sidStr}}`;
        result += sidStr;
      } else result += sidStr;
    }

    return result;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "RelativeObjectIdentifierValueBlock";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.toString();
    object.sidArray = [];

    for (let i = 0; i < this.value.length; i++) object.sidArray.push(this.value[i].toJSON());

    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends BaseBlock
 */


class RelativeObjectIdentifier extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "RelativeObjectIdentifier" class
   * @param {Object} [parameters={}]
   * @property {ArrayBuffer} [valueHex]
   */
  constructor(parameters = {}) {
    super(parameters, LocalRelativeObjectIdentifierValueBlock);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 13; // RELATIVE OBJECT IDENTIFIER
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "RelativeObjectIdentifier";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************

/**
 * @extends LocalBaseBlock
 * @extends HexBlock
 */


exports.RelativeObjectIdentifier = RelativeObjectIdentifier;

class LocalBmpStringValueBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalBmpStringValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.isHexOnly = true;
    this.value = "";
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "BmpStringValueBlock";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.value;
    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends BaseBlock
 */


class BmpString extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "BmpString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalBmpStringValueBlock);
    if ("value" in parameters) this.fromString(parameters.value);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 30; // BmpString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "BmpString";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    this.fromBuffer(this.valueBlock.valueHex);
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Function converting ArrayBuffer into ASN.1 internal string
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   */


  fromBuffer(inputBuffer) {
    //noinspection JSCheckFunctionSignatures
    const copyBuffer = inputBuffer.slice(0);
    const valueView = new Uint8Array(copyBuffer);

    for (let i = 0; i < valueView.length; i += 2) {
      const temp = valueView[i];
      valueView[i] = valueView[i + 1];
      valueView[i + 1] = temp;
    }

    this.valueBlock.value = String.fromCharCode.apply(null, new Uint16Array(copyBuffer));
  } //**********************************************************************************

  /**
   * Function converting JavaScript string into ASN.1 internal class
   * @param {!string} inputString ASN.1 BER encoded array
   */


  fromString(inputString) {
    const strLength = inputString.length;
    this.valueBlock.valueHex = new ArrayBuffer(strLength * 2);
    const valueHexView = new Uint8Array(this.valueBlock.valueHex);

    for (let i = 0; i < strLength; i++) {
      const codeBuf = (0, _pvutils.utilToBase)(inputString.charCodeAt(i), 8);
      const codeView = new Uint8Array(codeBuf);
      if (codeView.length > 2) continue;
      const dif = 2 - codeView.length;

      for (let j = codeView.length - 1; j >= 0; j--) valueHexView[i * 2 + j + dif] = codeView[j];
    }

    this.valueBlock.value = inputString;
  } //**********************************************************************************


} //**************************************************************************************


exports.BmpString = BmpString;

class LocalUniversalStringValueBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalUniversalStringValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.isHexOnly = true;
    this.value = "";
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "UniversalStringValueBlock";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.value;
    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends BaseBlock
 */


class UniversalString extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "UniversalString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalUniversalStringValueBlock);
    if ("value" in parameters) this.fromString(parameters.value);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 28; // UniversalString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "UniversalString";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    this.fromBuffer(this.valueBlock.valueHex);
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Function converting ArrayBuffer into ASN.1 internal string
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   */


  fromBuffer(inputBuffer) {
    //noinspection JSCheckFunctionSignatures
    const copyBuffer = inputBuffer.slice(0);
    const valueView = new Uint8Array(copyBuffer);

    for (let i = 0; i < valueView.length; i += 4) {
      valueView[i] = valueView[i + 3];
      valueView[i + 1] = valueView[i + 2];
      valueView[i + 2] = 0x00;
      valueView[i + 3] = 0x00;
    }

    this.valueBlock.value = String.fromCharCode.apply(null, new Uint32Array(copyBuffer));
  } //**********************************************************************************

  /**
   * Function converting JavaScript string into ASN.1 internal class
   * @param {!string} inputString ASN.1 BER encoded array
   */


  fromString(inputString) {
    const strLength = inputString.length;
    this.valueBlock.valueHex = new ArrayBuffer(strLength * 4);
    const valueHexView = new Uint8Array(this.valueBlock.valueHex);

    for (let i = 0; i < strLength; i++) {
      const codeBuf = (0, _pvutils.utilToBase)(inputString.charCodeAt(i), 8);
      const codeView = new Uint8Array(codeBuf);
      if (codeView.length > 4) continue;
      const dif = 4 - codeView.length;

      for (let j = codeView.length - 1; j >= 0; j--) valueHexView[i * 4 + j + dif] = codeView[j];
    }

    this.valueBlock.value = inputString;
  } //**********************************************************************************


} //**************************************************************************************


exports.UniversalString = UniversalString;

class LocalSimpleStringValueBlock extends HexBlock(LocalBaseBlock) {
  //**********************************************************************************

  /**
   * Constructor for "LocalSimpleStringValueBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.value = "";
    this.isHexOnly = true;
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "SimpleStringValueBlock";
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.value = this.value;
    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends BaseBlock
 */


class LocalSimpleStringBlock extends BaseBlock {
  //**********************************************************************************

  /**
   * Constructor for "LocalSimpleStringBlock" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters, LocalSimpleStringValueBlock);
    if ("value" in parameters) this.fromString(parameters.value);
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "SIMPLESTRING";
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    this.fromBuffer(this.valueBlock.valueHex);
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Function converting ArrayBuffer into ASN.1 internal string
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   */


  fromBuffer(inputBuffer) {
    this.valueBlock.value = String.fromCharCode.apply(null, new Uint8Array(inputBuffer));
  } //**********************************************************************************

  /**
   * Function converting JavaScript string into ASN.1 internal class
   * @param {!string} inputString ASN.1 BER encoded array
   */


  fromString(inputString) {
    const strLen = inputString.length;
    this.valueBlock.valueHex = new ArrayBuffer(strLen);
    const view = new Uint8Array(this.valueBlock.valueHex);

    for (let i = 0; i < strLen; i++) view[i] = inputString.charCodeAt(i);

    this.valueBlock.value = inputString;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


class NumericString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "NumericString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 18; // NumericString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "NumericString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.NumericString = NumericString;

class PrintableString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "PrintableString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 19; // PrintableString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "PrintableString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.PrintableString = PrintableString;

class TeletexString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "TeletexString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 20; // TeletexString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "TeletexString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.TeletexString = TeletexString;

class VideotexString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "VideotexString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 21; // VideotexString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "VideotexString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.VideotexString = VideotexString;

class IA5String extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "IA5String" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 22; // IA5String
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "IA5String";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.IA5String = IA5String;

class GraphicString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "GraphicString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 25; // GraphicString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "GraphicString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.GraphicString = GraphicString;

class VisibleString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "VisibleString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 26; // VisibleString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "VisibleString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.VisibleString = VisibleString;

class GeneralString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "GeneralString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 27; // GeneralString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "GeneralString";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends LocalSimpleStringBlock
 */


exports.GeneralString = GeneralString;

class CharacterString extends LocalSimpleStringBlock {
  //**********************************************************************************

  /**
   * Constructor for "CharacterString" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 29; // CharacterString
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "CharacterString";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of all date and time classes
//**************************************************************************************

/**
 * @extends VisibleString
 */


exports.CharacterString = CharacterString;

class UTCTime extends VisibleString {
  //**********************************************************************************

  /**
   * Constructor for "UTCTime" class
   * @param {Object} [parameters={}]
   * @property {string} [value] String representatio of the date
   * @property {Date} [valueDate] JavaScript "Date" object
   */
  constructor(parameters = {}) {
    super(parameters);
    this.year = 0;
    this.month = 0;
    this.day = 0;
    this.hour = 0;
    this.minute = 0;
    this.second = 0; //region Create UTCTime from ASN.1 UTC string value

    if ("value" in parameters) {
      this.fromString(parameters.value);
      this.valueBlock.valueHex = new ArrayBuffer(parameters.value.length);
      const view = new Uint8Array(this.valueBlock.valueHex);

      for (let i = 0; i < parameters.value.length; i++) view[i] = parameters.value.charCodeAt(i);
    } //endregion
    //region Create GeneralizedTime from JavaScript Date type


    if ("valueDate" in parameters) {
      this.fromDate(parameters.valueDate);
      this.valueBlock.valueHex = this.toBuffer();
    } //endregion


    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 23; // UTCTime
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    this.fromBuffer(this.valueBlock.valueHex);
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Function converting ArrayBuffer into ASN.1 internal string
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   */


  fromBuffer(inputBuffer) {
    this.fromString(String.fromCharCode.apply(null, new Uint8Array(inputBuffer)));
  } //**********************************************************************************

  /**
   * Function converting ASN.1 internal string into ArrayBuffer
   * @returns {ArrayBuffer}
   */


  toBuffer() {
    const str = this.toString();
    const buffer = new ArrayBuffer(str.length);
    const view = new Uint8Array(buffer);

    for (let i = 0; i < str.length; i++) view[i] = str.charCodeAt(i);

    return buffer;
  } //**********************************************************************************

  /**
   * Function converting "Date" object into ASN.1 internal string
   * @param {!Date} inputDate JavaScript "Date" object
   */


  fromDate(inputDate) {
    this.year = inputDate.getUTCFullYear();
    this.month = inputDate.getUTCMonth() + 1;
    this.day = inputDate.getUTCDate();
    this.hour = inputDate.getUTCHours();
    this.minute = inputDate.getUTCMinutes();
    this.second = inputDate.getUTCSeconds();
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Function converting ASN.1 internal string into "Date" object
   * @returns {Date}
   */


  toDate() {
    return new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second));
  } //**********************************************************************************

  /**
   * Function converting JavaScript string into ASN.1 internal class
   * @param {!string} inputString ASN.1 BER encoded array
   */


  fromString(inputString) {
    //region Parse input string
    const parser = /(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z/ig;
    const parserArray = parser.exec(inputString);

    if (parserArray === null) {
      this.error = "Wrong input string for convertion";
      return;
    } //endregion
    //region Store parsed values


    const year = parseInt(parserArray[1], 10);
    if (year >= 50) this.year = 1900 + year;else this.year = 2000 + year;
    this.month = parseInt(parserArray[2], 10);
    this.day = parseInt(parserArray[3], 10);
    this.hour = parseInt(parserArray[4], 10);
    this.minute = parseInt(parserArray[5], 10);
    this.second = parseInt(parserArray[6], 10); //endregion
  } //**********************************************************************************

  /**
   * Function converting ASN.1 internal class into JavaScript string
   * @returns {string}
   */


  toString() {
    const outputArray = new Array(7);
    outputArray[0] = (0, _pvutils.padNumber)(this.year < 2000 ? this.year - 1900 : this.year - 2000, 2);
    outputArray[1] = (0, _pvutils.padNumber)(this.month, 2);
    outputArray[2] = (0, _pvutils.padNumber)(this.day, 2);
    outputArray[3] = (0, _pvutils.padNumber)(this.hour, 2);
    outputArray[4] = (0, _pvutils.padNumber)(this.minute, 2);
    outputArray[5] = (0, _pvutils.padNumber)(this.second, 2);
    outputArray[6] = "Z";
    return outputArray.join("");
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "UTCTime";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.year = this.year;
    object.month = this.month;
    object.day = this.day;
    object.hour = this.hour;
    object.minute = this.minute;
    object.second = this.second;
    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends VisibleString
 */


exports.UTCTime = UTCTime;

class GeneralizedTime extends VisibleString {
  //**********************************************************************************

  /**
   * Constructor for "GeneralizedTime" class
   * @param {Object} [parameters={}]
   * @property {string} [value] String representatio of the date
   * @property {Date} [valueDate] JavaScript "Date" object
   */
  constructor(parameters = {}) {
    super(parameters);
    this.year = 0;
    this.month = 0;
    this.day = 0;
    this.hour = 0;
    this.minute = 0;
    this.second = 0;
    this.millisecond = 0; //region Create UTCTime from ASN.1 UTC string value

    if ("value" in parameters) {
      this.fromString(parameters.value);
      this.valueBlock.valueHex = new ArrayBuffer(parameters.value.length);
      const view = new Uint8Array(this.valueBlock.valueHex);

      for (let i = 0; i < parameters.value.length; i++) view[i] = parameters.value.charCodeAt(i);
    } //endregion
    //region Create GeneralizedTime from JavaScript Date type


    if ("valueDate" in parameters) {
      this.fromDate(parameters.valueDate);
      this.valueBlock.valueHex = this.toBuffer();
    } //endregion


    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 24; // GeneralizedTime
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length);

    if (resultOffset === -1) {
      this.error = this.valueBlock.error;
      return resultOffset;
    }

    this.fromBuffer(this.valueBlock.valueHex);
    if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength;
    if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength;
    if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength;
    return resultOffset;
  } //**********************************************************************************

  /**
   * Function converting ArrayBuffer into ASN.1 internal string
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   */


  fromBuffer(inputBuffer) {
    this.fromString(String.fromCharCode.apply(null, new Uint8Array(inputBuffer)));
  } //**********************************************************************************

  /**
   * Function converting ASN.1 internal string into ArrayBuffer
   * @returns {ArrayBuffer}
   */


  toBuffer() {
    const str = this.toString();
    const buffer = new ArrayBuffer(str.length);
    const view = new Uint8Array(buffer);

    for (let i = 0; i < str.length; i++) view[i] = str.charCodeAt(i);

    return buffer;
  } //**********************************************************************************

  /**
   * Function converting "Date" object into ASN.1 internal string
   * @param {!Date} inputDate JavaScript "Date" object
   */


  fromDate(inputDate) {
    this.year = inputDate.getUTCFullYear();
    this.month = inputDate.getUTCMonth() + 1;
    this.day = inputDate.getUTCDate();
    this.hour = inputDate.getUTCHours();
    this.minute = inputDate.getUTCMinutes();
    this.second = inputDate.getUTCSeconds();
    this.millisecond = inputDate.getUTCMilliseconds();
  } //**********************************************************************************
  //noinspection JSUnusedGlobalSymbols

  /**
   * Function converting ASN.1 internal string into "Date" object
   * @returns {Date}
   */


  toDate() {
    return new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.millisecond));
  } //**********************************************************************************

  /**
   * Function converting JavaScript string into ASN.1 internal class
   * @param {!string} inputString ASN.1 BER encoded array
   */


  fromString(inputString) {
    //region Initial variables
    let isUTC = false;
    let timeString = "";
    let dateTimeString = "";
    let fractionPart = 0;
    let parser;
    let hourDifference = 0;
    let minuteDifference = 0; //endregion
    //region Convert as UTC time

    if (inputString[inputString.length - 1] === "Z") {
      timeString = inputString.substr(0, inputString.length - 1);
      isUTC = true;
    } //endregion
    //region Convert as local time
    else {
        //noinspection JSPrimitiveTypeWrapperUsage
        const number = new Number(inputString[inputString.length - 1]);
        if (isNaN(number.valueOf())) throw new Error("Wrong input string for convertion");
        timeString = inputString;
      } //endregion
    //region Check that we do not have a "+" and "-" symbols inside UTC time


    if (isUTC) {
      if (timeString.indexOf("+") !== -1) throw new Error("Wrong input string for convertion");
      if (timeString.indexOf("-") !== -1) throw new Error("Wrong input string for convertion");
    } //endregion
    //region Get "UTC time difference" in case of local time
    else {
        let multiplier = 1;
        let differencePosition = timeString.indexOf("+");
        let differenceString = "";

        if (differencePosition === -1) {
          differencePosition = timeString.indexOf("-");
          multiplier = -1;
        }

        if (differencePosition !== -1) {
          differenceString = timeString.substr(differencePosition + 1);
          timeString = timeString.substr(0, differencePosition);
          if (differenceString.length !== 2 && differenceString.length !== 4) throw new Error("Wrong input string for convertion"); //noinspection JSPrimitiveTypeWrapperUsage

          let number = new Number(differenceString.substr(0, 2));
          if (isNaN(number.valueOf())) throw new Error("Wrong input string for convertion");
          hourDifference = multiplier * number;

          if (differenceString.length === 4) {
            //noinspection JSPrimitiveTypeWrapperUsage
            number = new Number(differenceString.substr(2, 2));
            if (isNaN(number.valueOf())) throw new Error("Wrong input string for convertion");
            minuteDifference = multiplier * number;
          }
        }
      } //endregion
    //region Get position of fraction point


    let fractionPointPosition = timeString.indexOf("."); // Check for "full stop" symbol

    if (fractionPointPosition === -1) fractionPointPosition = timeString.indexOf(","); // Check for "comma" symbol
    //endregion
    //region Get fraction part

    if (fractionPointPosition !== -1) {
      //noinspection JSPrimitiveTypeWrapperUsage
      const fractionPartCheck = new Number(`0${timeString.substr(fractionPointPosition)}`);
      if (isNaN(fractionPartCheck.valueOf())) throw new Error("Wrong input string for convertion");
      fractionPart = fractionPartCheck.valueOf();
      dateTimeString = timeString.substr(0, fractionPointPosition);
    } else dateTimeString = timeString; //endregion
    //region Parse internal date


    switch (true) {
      case dateTimeString.length === 8:
        // "YYYYMMDD"
        parser = /(\d{4})(\d{2})(\d{2})/ig;
        if (fractionPointPosition !== -1) throw new Error("Wrong input string for convertion"); // Here we should not have a "fraction point"

        break;

      case dateTimeString.length === 10:
        // "YYYYMMDDHH"
        parser = /(\d{4})(\d{2})(\d{2})(\d{2})/ig;

        if (fractionPointPosition !== -1) {
          let fractionResult = 60 * fractionPart;
          this.minute = Math.floor(fractionResult);
          fractionResult = 60 * (fractionResult - this.minute);
          this.second = Math.floor(fractionResult);
          fractionResult = 1000 * (fractionResult - this.second);
          this.millisecond = Math.floor(fractionResult);
        }

        break;

      case dateTimeString.length === 12:
        // "YYYYMMDDHHMM"
        parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/ig;

        if (fractionPointPosition !== -1) {
          let fractionResult = 60 * fractionPart;
          this.second = Math.floor(fractionResult);
          fractionResult = 1000 * (fractionResult - this.second);
          this.millisecond = Math.floor(fractionResult);
        }

        break;

      case dateTimeString.length === 14:
        // "YYYYMMDDHHMMSS"
        parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ig;

        if (fractionPointPosition !== -1) {
          const fractionResult = 1000 * fractionPart;
          this.millisecond = Math.floor(fractionResult);
        }

        break;

      default:
        throw new Error("Wrong input string for convertion");
    } //endregion
    //region Put parsed values at right places


    const parserArray = parser.exec(dateTimeString);
    if (parserArray === null) throw new Error("Wrong input string for convertion");

    for (let j = 1; j < parserArray.length; j++) {
      switch (j) {
        case 1:
          this.year = parseInt(parserArray[j], 10);
          break;

        case 2:
          this.month = parseInt(parserArray[j], 10);
          break;

        case 3:
          this.day = parseInt(parserArray[j], 10);
          break;

        case 4:
          this.hour = parseInt(parserArray[j], 10) + hourDifference;
          break;

        case 5:
          this.minute = parseInt(parserArray[j], 10) + minuteDifference;
          break;

        case 6:
          this.second = parseInt(parserArray[j], 10);
          break;

        default:
          throw new Error("Wrong input string for convertion");
      }
    } //endregion
    //region Get final date


    if (isUTC === false) {
      const tempDate = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
      this.year = tempDate.getUTCFullYear();
      this.month = tempDate.getUTCMonth();
      this.day = tempDate.getUTCDay();
      this.hour = tempDate.getUTCHours();
      this.minute = tempDate.getUTCMinutes();
      this.second = tempDate.getUTCSeconds();
      this.millisecond = tempDate.getUTCMilliseconds();
    } //endregion

  } //**********************************************************************************

  /**
   * Function converting ASN.1 internal class into JavaScript string
   * @returns {string}
   */


  toString() {
    const outputArray = [];
    outputArray.push((0, _pvutils.padNumber)(this.year, 4));
    outputArray.push((0, _pvutils.padNumber)(this.month, 2));
    outputArray.push((0, _pvutils.padNumber)(this.day, 2));
    outputArray.push((0, _pvutils.padNumber)(this.hour, 2));
    outputArray.push((0, _pvutils.padNumber)(this.minute, 2));
    outputArray.push((0, _pvutils.padNumber)(this.second, 2));

    if (this.millisecond !== 0) {
      outputArray.push(".");
      outputArray.push((0, _pvutils.padNumber)(this.millisecond, 3));
    }

    outputArray.push("Z");
    return outputArray.join("");
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "GeneralizedTime";
  } //**********************************************************************************

  /**
   * Convertion for the block to JSON object
   * @returns {Object}
   */


  toJSON() {
    let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object

    try {
      object = super.toJSON();
    } catch (ex) {} //endregion


    object.year = this.year;
    object.month = this.month;
    object.day = this.day;
    object.hour = this.hour;
    object.minute = this.minute;
    object.second = this.second;
    object.millisecond = this.millisecond;
    return object;
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends Utf8String
 */


exports.GeneralizedTime = GeneralizedTime;

class DATE extends Utf8String {
  //**********************************************************************************

  /**
   * Constructor for "DATE" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 31; // DATE
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "DATE";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends Utf8String
 */


exports.DATE = DATE;

class TimeOfDay extends Utf8String {
  //**********************************************************************************

  /**
   * Constructor for "TimeOfDay" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 32; // TimeOfDay
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "TimeOfDay";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends Utf8String
 */


exports.TimeOfDay = TimeOfDay;

class DateTime extends Utf8String {
  //**********************************************************************************

  /**
   * Constructor for "DateTime" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 33; // DateTime
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "DateTime";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends Utf8String
 */


exports.DateTime = DateTime;

class Duration extends Utf8String {
  //**********************************************************************************

  /**
   * Constructor for "Duration" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 34; // Duration
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "Duration";
  } //**********************************************************************************


} //**************************************************************************************

/**
 * @extends Utf8String
 */


exports.Duration = Duration;

class TIME extends Utf8String {
  //**********************************************************************************

  /**
   * Constructor for "Time" class
   * @param {Object} [parameters={}]
   */
  constructor(parameters = {}) {
    super(parameters);
    this.idBlock.tagClass = 1; // UNIVERSAL

    this.idBlock.tagNumber = 14; // Time
  } //**********************************************************************************

  /**
   * Aux function, need to get a block name. Need to have it here for inhiritence
   * @returns {string}
   */


  static blockName() {
    return "TIME";
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type Choice
//**************************************************************************************


exports.TIME = TIME;

class Choice {
  //**********************************************************************************

  /**
   * Constructor for "Choice" class
   * @param {Object} [parameters={}]
   * @property {Array} [value] Array of ASN.1 types for make a choice from
   * @property {boolean} [optional]
   */
  constructor(parameters = {}) {
    this.value = (0, _pvutils.getParametersValue)(parameters, "value", []);
    this.optional = (0, _pvutils.getParametersValue)(parameters, "optional", false);
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type Any
//**************************************************************************************


exports.Choice = Choice;

class Any {
  //**********************************************************************************

  /**
   * Constructor for "Any" class
   * @param {Object} [parameters={}]
   * @property {string} [name]
   * @property {boolean} [optional]
   */
  constructor(parameters = {}) {
    this.name = (0, _pvutils.getParametersValue)(parameters, "name", "");
    this.optional = (0, _pvutils.getParametersValue)(parameters, "optional", false);
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type Repeated
//**************************************************************************************


exports.Any = Any;

class Repeated {
  //**********************************************************************************

  /**
   * Constructor for "Repeated" class
   * @param {Object} [parameters={}]
   * @property {string} [name]
   * @property {boolean} [optional]
   */
  constructor(parameters = {}) {
    this.name = (0, _pvutils.getParametersValue)(parameters, "name", "");
    this.optional = (0, _pvutils.getParametersValue)(parameters, "optional", false);
    this.value = (0, _pvutils.getParametersValue)(parameters, "value", new Any());
    this.local = (0, _pvutils.getParametersValue)(parameters, "local", false); // Could local or global array to store elements
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type RawData
//**************************************************************************************

/**
 * @description Special class providing ability to have "toBER/fromBER" for raw ArrayBuffer
 */


exports.Repeated = Repeated;

class RawData {
  //**********************************************************************************

  /**
   * Constructor for "Repeated" class
   * @param {Object} [parameters={}]
   * @property {string} [name]
   * @property {boolean} [optional]
   */
  constructor(parameters = {}) {
    this.data = (0, _pvutils.getParametersValue)(parameters, "data", new ArrayBuffer(0));
  } //**********************************************************************************

  /**
   * Base function for converting block from BER encoded array of bytes
   * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
   * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
   * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
   * @returns {number} Offset after least decoded byte
   */


  fromBER(inputBuffer, inputOffset, inputLength) {
    this.data = inputBuffer.slice(inputOffset, inputLength);
    return inputOffset + inputLength;
  } //**********************************************************************************

  /**
   * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
   * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
   * @returns {ArrayBuffer}
   */


  toBER(sizeOnly = false) {
    return this.data;
  } //**********************************************************************************


} //**************************************************************************************
//endregion
//**************************************************************************************
//region Major ASN.1 BER decoding function
//**************************************************************************************

/**
 * Internal library function for decoding ASN.1 BER
 * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
 * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
 * @param {!number} inputLength Maximum length of array of bytes which can be using in this function
 * @returns {{offset: number, result: Object}}
 */


exports.RawData = RawData;

function LocalFromBER(inputBuffer, inputOffset, inputLength) {
  const incomingOffset = inputOffset; // Need to store initial offset since "inputOffset" is changing in the function
  //region Local function changing a type for ASN.1 classes

  function localChangeType(inputObject, newType) {
    if (inputObject instanceof newType) return inputObject;
    const newObject = new newType();
    newObject.idBlock = inputObject.idBlock;
    newObject.lenBlock = inputObject.lenBlock;
    newObject.warnings = inputObject.warnings; //noinspection JSCheckFunctionSignatures

    newObject.valueBeforeDecode = inputObject.valueBeforeDecode.slice(0);
    return newObject;
  } //endregion
  //region Create a basic ASN.1 type since we need to return errors and warnings from the function


  let returnObject = new BaseBlock({}, Object); //endregion
  //region Basic check for parameters

  const baseBlock = new LocalBaseBlock();

  if ((0, _pvutils.checkBufferParams)(baseBlock, inputBuffer, inputOffset, inputLength) === false) {
    returnObject.error = baseBlock.error;
    return {
      offset: -1,
      result: returnObject
    };
  } //endregion
  //region Getting Uint8Array from ArrayBuffer


  const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion
  //region Initial checks

  if (intBuffer.length === 0) {
    this.error = "Zero buffer length";
    return {
      offset: -1,
      result: returnObject
    };
  } //endregion
  //region Decode indentifcation block of ASN.1 BER structure


  let resultOffset = returnObject.idBlock.fromBER(inputBuffer, inputOffset, inputLength);
  returnObject.warnings.concat(returnObject.idBlock.warnings);

  if (resultOffset === -1) {
    returnObject.error = returnObject.idBlock.error;
    return {
      offset: -1,
      result: returnObject
    };
  }

  inputOffset = resultOffset;
  inputLength -= returnObject.idBlock.blockLength; //endregion
  //region Decode length block of ASN.1 BER structure

  resultOffset = returnObject.lenBlock.fromBER(inputBuffer, inputOffset, inputLength);
  returnObject.warnings.concat(returnObject.lenBlock.warnings);

  if (resultOffset === -1) {
    returnObject.error = returnObject.lenBlock.error;
    return {
      offset: -1,
      result: returnObject
    };
  }

  inputOffset = resultOffset;
  inputLength -= returnObject.lenBlock.blockLength; //endregion
  //region Check for usign indefinite length form in encoding for primitive types

  if (returnObject.idBlock.isConstructed === false && returnObject.lenBlock.isIndefiniteForm === true) {
    returnObject.error = "Indefinite length form used for primitive encoding form";
    return {
      offset: -1,
      result: returnObject
    };
  } //endregion
  //region Switch ASN.1 block type


  let newASN1Type = BaseBlock;

  switch (returnObject.idBlock.tagClass) {
    //region UNIVERSAL
    case 1:
      //region Check for reserved tag numbers
      if (returnObject.idBlock.tagNumber >= 37 && returnObject.idBlock.isHexOnly === false) {
        returnObject.error = "UNIVERSAL 37 and upper tags are reserved by ASN.1 standard";
        return {
          offset: -1,
          result: returnObject
        };
      } //endregion


      switch (returnObject.idBlock.tagNumber) {
        //region EndOfContent type
        case 0:
          //region Check for EndOfContent type
          if (returnObject.idBlock.isConstructed === true && returnObject.lenBlock.length > 0) {
            returnObject.error = "Type [UNIVERSAL 0] is reserved";
            return {
              offset: -1,
              result: returnObject
            };
          } //endregion


          newASN1Type = EndOfContent;
          break;
        //endregion
        //region Boolean type

        case 1:
          newASN1Type = Boolean;
          break;
        //endregion
        //region Integer type

        case 2:
          newASN1Type = Integer;
          break;
        //endregion
        //region BitString type

        case 3:
          newASN1Type = BitString;
          break;
        //endregion
        //region OctetString type

        case 4:
          newASN1Type = OctetString;
          break;
        //endregion
        //region Null type

        case 5:
          newASN1Type = Null;
          break;
        //endregion
        //region OBJECT IDENTIFIER type

        case 6:
          newASN1Type = ObjectIdentifier;
          break;
        //endregion
        //region Enumerated type

        case 10:
          newASN1Type = Enumerated;
          break;
        //endregion
        //region Utf8String type

        case 12:
          newASN1Type = Utf8String;
          break;
        //endregion
        //region Time type
        //region RELATIVE OBJECT IDENTIFIER type

        case 13:
          newASN1Type = RelativeObjectIdentifier;
          break;
        //endregion

        case 14:
          newASN1Type = TIME;
          break;
        //endregion
        //region ASN.1 reserved type

        case 15:
          returnObject.error = "[UNIVERSAL 15] is reserved by ASN.1 standard";
          return {
            offset: -1,
            result: returnObject
          };
        //endregion
        //region Sequence type

        case 16:
          newASN1Type = Sequence;
          break;
        //endregion
        //region Set type

        case 17:
          newASN1Type = Set;
          break;
        //endregion
        //region NumericString type

        case 18:
          newASN1Type = NumericString;
          break;
        //endregion
        //region PrintableString type

        case 19:
          newASN1Type = PrintableString;
          break;
        //endregion
        //region TeletexString type

        case 20:
          newASN1Type = TeletexString;
          break;
        //endregion
        //region VideotexString type

        case 21:
          newASN1Type = VideotexString;
          break;
        //endregion
        //region IA5String type

        case 22:
          newASN1Type = IA5String;
          break;
        //endregion
        //region UTCTime type

        case 23:
          newASN1Type = UTCTime;
          break;
        //endregion
        //region GeneralizedTime type

        case 24:
          newASN1Type = GeneralizedTime;
          break;
        //endregion
        //region GraphicString type

        case 25:
          newASN1Type = GraphicString;
          break;
        //endregion
        //region VisibleString type

        case 26:
          newASN1Type = VisibleString;
          break;
        //endregion
        //region GeneralString type

        case 27:
          newASN1Type = GeneralString;
          break;
        //endregion
        //region UniversalString type

        case 28:
          newASN1Type = UniversalString;
          break;
        //endregion
        //region CharacterString type

        case 29:
          newASN1Type = CharacterString;
          break;
        //endregion
        //region BmpString type

        case 30:
          newASN1Type = BmpString;
          break;
        //endregion
        //region DATE type

        case 31:
          newASN1Type = DATE;
          break;
        //endregion
        //region TimeOfDay type

        case 32:
          newASN1Type = TimeOfDay;
          break;
        //endregion
        //region Date-Time type

        case 33:
          newASN1Type = DateTime;
          break;
        //endregion
        //region Duration type

        case 34:
          newASN1Type = Duration;
          break;
        //endregion
        //region default

        default:
          {
            let newObject;
            if (returnObject.idBlock.isConstructed === true) newObject = new Constructed();else newObject = new Primitive();
            newObject.idBlock = returnObject.idBlock;
            newObject.lenBlock = returnObject.lenBlock;
            newObject.warnings = returnObject.warnings;
            returnObject = newObject;
            resultOffset = returnObject.fromBER(inputBuffer, inputOffset, inputLength);
          }
        //endregion
      }

      break;
    //endregion
    //region All other tag classes

    case 2: // APPLICATION

    case 3: // CONTEXT-SPECIFIC

    case 4: // PRIVATE

    default:
      {
        if (returnObject.idBlock.isConstructed === true) newASN1Type = Constructed;else newASN1Type = Primitive;
      }
    //endregion
  } //endregion
  //region Change type and perform BER decoding


  returnObject = localChangeType(returnObject, newASN1Type);
  resultOffset = returnObject.fromBER(inputBuffer, inputOffset, returnObject.lenBlock.isIndefiniteForm === true ? inputLength : returnObject.lenBlock.length); //endregion
  //region Coping incoming buffer for entire ASN.1 block

  returnObject.valueBeforeDecode = inputBuffer.slice(incomingOffset, incomingOffset + returnObject.blockLength); //endregion

  return {
    offset: resultOffset,
    result: returnObject
  };
} //**************************************************************************************

/**
 * Major function for decoding ASN.1 BER array into internal library structuries
 * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array of bytes
 */


function fromBER(inputBuffer) {
  if (inputBuffer.byteLength === 0) {
    const result = new BaseBlock({}, Object);
    result.error = "Input buffer has zero length";
    return {
      offset: -1,
      result
    };
  }

  return LocalFromBER(inputBuffer, 0, inputBuffer.byteLength);
} //**************************************************************************************
//endregion
//**************************************************************************************
//region Major scheme verification function
//**************************************************************************************

/**
 * Compare of two ASN.1 object trees
 * @param {!Object} root Root of input ASN.1 object tree
 * @param {!Object} inputData Input ASN.1 object tree
 * @param {!Object} inputSchema Input ASN.1 schema to compare with
 * @return {{verified: boolean}|{verified:boolean, result: Object}}
 */


function compareSchema(root, inputData, inputSchema) {
  //region Special case for Choice schema element type
  if (inputSchema instanceof Choice) {
    const choiceResult = false;

    for (let j = 0; j < inputSchema.value.length; j++) {
      const result = compareSchema(root, inputData, inputSchema.value[j]);

      if (result.verified === true) {
        return {
          verified: true,
          result: root
        };
      }
    }

    if (choiceResult === false) {
      const _result = {
        verified: false,
        result: {
          error: "Wrong values for Choice type"
        }
      };
      if (inputSchema.hasOwnProperty("name")) _result.name = inputSchema.name;
      return _result;
    }
  } //endregion
  //region Special case for Any schema element type


  if (inputSchema instanceof Any) {
    //region Add named component of ASN.1 schema
    if (inputSchema.hasOwnProperty("name")) root[inputSchema.name] = inputData; //endregion

    return {
      verified: true,
      result: root
    };
  } //endregion
  //region Initial check


  if (root instanceof Object === false) {
    return {
      verified: false,
      result: {
        error: "Wrong root object"
      }
    };
  }

  if (inputData instanceof Object === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 data"
      }
    };
  }

  if (inputSchema instanceof Object === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  }

  if ("idBlock" in inputSchema === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  } //endregion
  //region Comparing idBlock properties in ASN.1 data and ASN.1 schema
  //region Encode and decode ASN.1 schema idBlock
  /// <remarks>This encoding/decoding is neccessary because could be an errors in schema definition</remarks>


  if ("fromBER" in inputSchema.idBlock === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  }

  if ("toBER" in inputSchema.idBlock === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  }

  const encodedId = inputSchema.idBlock.toBER(false);

  if (encodedId.byteLength === 0) {
    return {
      verified: false,
      result: {
        error: "Error encoding idBlock for ASN.1 schema"
      }
    };
  }

  const decodedOffset = inputSchema.idBlock.fromBER(encodedId, 0, encodedId.byteLength);

  if (decodedOffset === -1) {
    return {
      verified: false,
      result: {
        error: "Error decoding idBlock for ASN.1 schema"
      }
    };
  } //endregion
  //region tagClass


  if (inputSchema.idBlock.hasOwnProperty("tagClass") === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  }

  if (inputSchema.idBlock.tagClass !== inputData.idBlock.tagClass) {
    return {
      verified: false,
      result: root
    };
  } //endregion
  //region tagNumber


  if (inputSchema.idBlock.hasOwnProperty("tagNumber") === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  }

  if (inputSchema.idBlock.tagNumber !== inputData.idBlock.tagNumber) {
    return {
      verified: false,
      result: root
    };
  } //endregion
  //region isConstructed


  if (inputSchema.idBlock.hasOwnProperty("isConstructed") === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema"
      }
    };
  }

  if (inputSchema.idBlock.isConstructed !== inputData.idBlock.isConstructed) {
    return {
      verified: false,
      result: root
    };
  } //endregion
  //region isHexOnly


  if ("isHexOnly" in inputSchema.idBlock === false) // Since 'isHexOnly' is an inhirited property
    {
      return {
        verified: false,
        result: {
          error: "Wrong ASN.1 schema"
        }
      };
    }

  if (inputSchema.idBlock.isHexOnly !== inputData.idBlock.isHexOnly) {
    return {
      verified: false,
      result: root
    };
  } //endregion
  //region valueHex


  if (inputSchema.idBlock.isHexOnly === true) {
    if ("valueHex" in inputSchema.idBlock === false) // Since 'valueHex' is an inhirited property
      {
        return {
          verified: false,
          result: {
            error: "Wrong ASN.1 schema"
          }
        };
      }

    const schemaView = new Uint8Array(inputSchema.idBlock.valueHex);
    const asn1View = new Uint8Array(inputData.idBlock.valueHex);

    if (schemaView.length !== asn1View.length) {
      return {
        verified: false,
        result: root
      };
    }

    for (let i = 0; i < schemaView.length; i++) {
      if (schemaView[i] !== asn1View[1]) {
        return {
          verified: false,
          result: root
        };
      }
    }
  } //endregion
  //endregion
  //region Add named component of ASN.1 schema


  if (inputSchema.hasOwnProperty("name")) {
    inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");
    if (inputSchema.name !== "") root[inputSchema.name] = inputData;
  } //endregion
  //region Getting next ASN.1 block for comparition


  if (inputSchema.idBlock.isConstructed === true) {
    let admission = 0;
    let result = {
      verified: false
    };
    let maxLength = inputSchema.valueBlock.value.length;

    if (maxLength > 0) {
      if (inputSchema.valueBlock.value[0] instanceof Repeated) maxLength = inputData.valueBlock.value.length;
    } //region Special case when constructive value has no elements


    if (maxLength === 0) {
      return {
        verified: true,
        result: root
      };
    } //endregion
    //region Special case when "inputData" has no values and "inputSchema" has all optional values


    if (inputData.valueBlock.value.length === 0 && inputSchema.valueBlock.value.length !== 0) {
      let _optional = true;

      for (let i = 0; i < inputSchema.valueBlock.value.length; i++) _optional = _optional && (inputSchema.valueBlock.value[i].optional || false);

      if (_optional === true) {
        return {
          verified: true,
          result: root
        };
      } //region Delete early added name of block


      if (inputSchema.hasOwnProperty("name")) {
        inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");
        if (inputSchema.name !== "") delete root[inputSchema.name];
      } //endregion


      root.error = "Inconsistent object length";
      return {
        verified: false,
        result: root
      };
    } //endregion


    for (let i = 0; i < maxLength; i++) {
      //region Special case when there is an "optional" element of ASN.1 schema at the end
      if (i - admission >= inputData.valueBlock.value.length) {
        if (inputSchema.valueBlock.value[i].optional === false) {
          const _result = {
            verified: false,
            result: root
          };
          root.error = "Inconsistent length between ASN.1 data and schema"; //region Delete early added name of block

          if (inputSchema.hasOwnProperty("name")) {
            inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");

            if (inputSchema.name !== "") {
              delete root[inputSchema.name];
              _result.name = inputSchema.name;
            }
          } //endregion


          return _result;
        }
      } //endregion
      else {
          //region Special case for Repeated type of ASN.1 schema element
          if (inputSchema.valueBlock.value[0] instanceof Repeated) {
            result = compareSchema(root, inputData.valueBlock.value[i], inputSchema.valueBlock.value[0].value);

            if (result.verified === false) {
              if (inputSchema.valueBlock.value[0].optional === true) admission++;else {
                //region Delete early added name of block
                if (inputSchema.hasOwnProperty("name")) {
                  inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");
                  if (inputSchema.name !== "") delete root[inputSchema.name];
                } //endregion


                return result;
              }
            }

            if ("name" in inputSchema.valueBlock.value[0] && inputSchema.valueBlock.value[0].name.length > 0) {
              let arrayRoot = {};
              if ("local" in inputSchema.valueBlock.value[0] && inputSchema.valueBlock.value[0].local === true) arrayRoot = inputData;else arrayRoot = root;
              if (typeof arrayRoot[inputSchema.valueBlock.value[0].name] === "undefined") arrayRoot[inputSchema.valueBlock.value[0].name] = [];
              arrayRoot[inputSchema.valueBlock.value[0].name].push(inputData.valueBlock.value[i]);
            }
          } //endregion
          else {
              result = compareSchema(root, inputData.valueBlock.value[i - admission], inputSchema.valueBlock.value[i]);

              if (result.verified === false) {
                if (inputSchema.valueBlock.value[i].optional === true) admission++;else {
                  //region Delete early added name of block
                  if (inputSchema.hasOwnProperty("name")) {
                    inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");
                    if (inputSchema.name !== "") delete root[inputSchema.name];
                  } //endregion


                  return result;
                }
              }
            }
        }
    }

    if (result.verified === false) // The situation may take place if last element is "optional" and verification failed
      {
        const _result = {
          verified: false,
          result: root
        }; //region Delete early added name of block

        if (inputSchema.hasOwnProperty("name")) {
          inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");

          if (inputSchema.name !== "") {
            delete root[inputSchema.name];
            _result.name = inputSchema.name;
          }
        } //endregion


        return _result;
      }

    return {
      verified: true,
      result: root
    };
  } //endregion
  //region Ability to parse internal value for primitive-encoded value (value of OctetString, for example)


  if ("primitiveSchema" in inputSchema && "valueHex" in inputData.valueBlock) {
    //region Decoding of raw ASN.1 data
    const asn1 = fromBER(inputData.valueBlock.valueHex);

    if (asn1.offset === -1) {
      const _result = {
        verified: false,
        result: asn1.result
      }; //region Delete early added name of block

      if (inputSchema.hasOwnProperty("name")) {
        inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, "");

        if (inputSchema.name !== "") {
          delete root[inputSchema.name];
          _result.name = inputSchema.name;
        }
      } //endregion


      return _result;
    } //endregion


    return compareSchema(root, asn1.result, inputSchema.primitiveSchema);
  }

  return {
    verified: true,
    result: root
  }; //endregion
} //**************************************************************************************
//noinspection JSUnusedGlobalSymbols

/**
 * ASN.1 schema verification for ArrayBuffer data
 * @param {!ArrayBuffer} inputBuffer Input BER-encoded ASN.1 data
 * @param {!Object} inputSchema Input ASN.1 schema to verify against to
 * @return {{verified: boolean}|{verified:boolean, result: Object}}
 */


function verifySchema(inputBuffer, inputSchema) {
  //region Initial check
  if (inputSchema instanceof Object === false) {
    return {
      verified: false,
      result: {
        error: "Wrong ASN.1 schema type"
      }
    };
  } //endregion
  //region Decoding of raw ASN.1 data


  const asn1 = fromBER(inputBuffer);

  if (asn1.offset === -1) {
    return {
      verified: false,
      result: asn1.result
    };
  } //endregion
  //region Compare ASN.1 struct with input schema


  return compareSchema(asn1.result, asn1.result, inputSchema); //endregion
} //**************************************************************************************
//endregion
//**************************************************************************************
//region Major function converting JSON to ASN.1 objects
//**************************************************************************************
//noinspection JSUnusedGlobalSymbols

/**
 * Converting from JSON to ASN.1 objects
 * @param {string|Object} json JSON string or object to convert to ASN.1 objects
 */


function fromJSON(json) {} // TODO Implement
//**************************************************************************************
//endregion
//**************************************************************************************
//# sourceMappingURL=asn1.js.map