Bug 1559225 - Build a certificate chain. r=johannh,keeler
authorCarolina <carolina.jimenez.g@gmail.com>
Fri, 12 Jul 2019 12:17:04 +0000
changeset 546334 9fc6f2b939d4edc7d09f10e096d46fb4e01ce382
parent 546333 5ac4ba7cf676a20f6fe6b47488c09145010f1991
child 546335 835ac3ae20ba6c1f55c4e0bd7adeb133c45e0904
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh, keeler
bugs1559225
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1559225 - Build a certificate chain. r=johannh,keeler Added third party libraries using browserify, builds a certificate chain using some functions defined in https://github.com/april/certainly-something and using a dummy certificate chain. r=johannh Differential Revision: https://phabricator.services.mozilla.com/D34927
.hgignore
browser/base/content/test/static/browser_parsable_script.js
toolkit/components/certviewer/content/.eslintrc.js
toolkit/components/certviewer/content/README.md
toolkit/components/certviewer/content/asn1js.js
toolkit/components/certviewer/content/certDecoder.js
toolkit/components/certviewer/content/certviewer.html
toolkit/components/certviewer/content/certviewer.js
toolkit/components/certviewer/content/components/certificate-section.js
toolkit/components/certviewer/content/components/dummy-info.js
toolkit/components/certviewer/content/ctlognames.js
toolkit/components/certviewer/content/package.json
toolkit/components/certviewer/content/pkijs.js
toolkit/components/certviewer/content/pvutils.js
toolkit/components/certviewer/content/strings.js
toolkit/components/certviewer/content/utils.js
toolkit/components/certviewer/content/vendor/asn1js_bundle.js
toolkit/components/certviewer/content/vendor/pkijs_bundle.js
toolkit/components/certviewer/content/vendor/pvutils_bundle.js
toolkit/components/certviewer/jar.mn
tools/rewriting/ThirdPartyPaths.txt
--- a/.hgignore
+++ b/.hgignore
@@ -193,8 +193,12 @@ tps_result\.json
 ^testing/raptor/raptor/tests/json/
 ^testing/raptor/webext/raptor/auto_gen_test_config.js
 
 # Ignore browsertime output directory
 ^browsertime-results
 
 # Ignore the build directories of WebRender standalone builds.
 gfx/wr/target/
+
+# Ignore this files in certviewer
+toolkit/components/certviewer/content/node_modules/
+toolkit/components/certviewer/content/package-lock.json
--- a/browser/base/content/test/static/browser_parsable_script.js
+++ b/browser/base/content/test/static/browser_parsable_script.js
@@ -15,16 +15,18 @@ const kWhitelist = new Set([
 const kESModuleList = new Set([
   /browser\/res\/payments\/(components|containers|mixins)\/.*\.js$/,
   /browser\/res\/payments\/paymentRequest\.js$/,
   /browser\/res\/payments\/PaymentsStore\.js$/,
   /browser\/aboutlogins\/components\/.*\.js$/,
   /browser\/aboutlogins\/.*\.js$/,
   /browser\/protections.js$/,
   /browser\/lockwise-card.js$/,
+  /toolkit\/content\/global\/certviewer\/components\/.*\.js$/,
+  /toolkit\/content\/global\/certviewer\/.*\.js$/,
 ]);
 
 // Normally we would use reflect.jsm to get Reflect.parse. However, if
 // we do that, then all the AST data is allocated in reflect.jsm's
 // zone. That exposes a bug in our GC. The GC collects reflect.jsm's
 // zone but not the zone in which our test code lives (since no new
 // data is being allocated in it). The cross-compartment wrappers in
 // our zone that point to the AST data never get collected, and so the
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/.eslintrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+  "parserOptions": {
+    "sourceType": "module",
+  },
+  "env": {
+    "node": true
+  },
+  "globals": {
+    "asn1js": true,
+    "pvutils": true,
+    "pkijs": true,
+  },
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/README.md
@@ -0,0 +1,14 @@
+# Certificate Viewer
+
+## Dependencies
+
+[PKI.js](https://github.com/PeculiarVentures/PKI.js)
+[ASN1.js](https://github.com/PeculiarVentures/ASN1.js)
+[pvutils.js](https://github.com/PeculiarVentures/pvutils)
+[Browserify](http://browserify.org/)
+
+## Updating dependencies
+
+Install all the dependencies doing `npm i`.
+
+Run `npm run build` any time you add something new to `pvutils.js`, `pkijs.js`, `asn1.js` or any other file required in that one.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/asn1js.js
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const asn1js = require("asn1js"); // version 2.0.22
+
+module.exports = {
+  asn1js,
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/certDecoder.js
@@ -0,0 +1,555 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { fromBER } = asn1js.asn1js;
+const { Certificate } = pkijs.pkijs;
+import {
+  b64urltodec,
+  b64urltohex,
+  getObjPath,
+  hash,
+  hashify,
+} from "chrome://global/content/certviewer/utils.js";
+import { strings } from "chrome://global/content/certviewer/strings.js";
+import { ctLogNames } from "chrome://global/content/certviewer/ctlognames.js";
+
+const getTimeZone = () => {
+  let timeZone = new Date().toString().match(/\(([A-Za-z\s].*)\)/);
+  if (timeZone === null) {
+    // America/Chicago
+    timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+  } else if (timeZone.length > 1) {
+    timeZone = timeZone[1]; // Central Daylight Time
+  } else {
+    timeZone = "Local Time"; // not sure if this is right, but let's go with it for now
+  }
+  return timeZone;
+};
+
+const getPublicKeyInfo = x509 => {
+  let spki = Object.assign(
+    {
+      crv: undefined,
+      e: undefined,
+      kty: undefined,
+      n: undefined,
+      keysize: undefined,
+      x: undefined,
+      xy: undefined,
+      y: undefined,
+    },
+    x509.subjectPublicKeyInfo
+  );
+
+  if (spki.kty === "RSA") {
+    spki.e = b64urltodec(spki.e); // exponent
+    spki.keysize = b64urltohex(spki.n).length * 8; // key size in bits
+    spki.n = hashify(b64urltohex(spki.n)); // modulus
+  } else if (spki.kty === "EC") {
+    spki.kty = "Elliptic Curve";
+    spki.keysize = parseInt(spki.crv.split("-")[1]); // this is a bit hacky
+    spki.x = hashify(b64urltohex(spki.x)); // x coordinate
+    spki.y = hashify(b64urltohex(spki.y)); // y coordinate
+    spki.xy = `04:${spki.x}:${spki.y}`; // 04 (uncompressed) public key
+  }
+  return spki;
+};
+
+const getX509Ext = (extensions, v) => {
+  for (var extension in extensions) {
+    if (extensions[extension].extnID === v) {
+      return extensions[extension];
+    }
+  }
+  return {
+    extnValue: undefined,
+    parsedValue: undefined,
+  };
+};
+
+const getKeyUsages = (x509, criticalExtensions) => {
+  let keyUsages = {
+    critical: criticalExtensions.includes("2.5.29.15"),
+    purposes: [],
+  };
+
+  let keyUsagesBS = getX509Ext(x509.extensions, "2.5.29.15").parsedValue;
+  if (keyUsagesBS !== undefined) {
+    // parse the bit string, shifting as necessary
+    let unusedBits = keyUsagesBS.valueBlock.unusedBits;
+    keyUsagesBS = parseInt(keyUsagesBS.valueBlock.valueHex, 16) >> unusedBits;
+
+    // iterate through the bit string
+    strings.keyUsages.slice(unusedBits - 1).forEach(usage => {
+      if (keyUsagesBS & 1) {
+        keyUsages.purposes.push(usage);
+      }
+
+      keyUsagesBS = keyUsagesBS >> 1;
+    });
+
+    // reverse the order for legibility
+    keyUsages.purposes.reverse();
+  }
+
+  return keyUsages;
+};
+
+const parseSubsidiary = distinguishedNames => {
+  const subsidiary = {
+    cn: "",
+    dn: [],
+    entries: [],
+  };
+
+  distinguishedNames.forEach(dn => {
+    const name = strings.names[dn.type];
+    const value = dn.value.valueBlock.value;
+
+    if (name === undefined) {
+      subsidiary.dn.push(`OID.${dn.type}=${value}`);
+      subsidiary.entries.push([`OID.${dn.type}`, value]);
+    } else if (name.short === undefined) {
+      subsidiary.dn.push(`OID.${dn.type}=${value}`);
+      subsidiary.entries.push([name.long, value]);
+    } else {
+      subsidiary.dn.push(`${name.short}=${value}`);
+      subsidiary.entries.push([name.long, value]);
+
+      // add the common name for tab display
+      if (name.short === "cn") {
+        subsidiary.cn = value;
+      }
+    }
+  });
+
+  // turn path into a string
+  subsidiary.dn = subsidiary.dn.join(", ");
+
+  return subsidiary;
+};
+
+const getSubjectAltNames = (x509, criticalExtensions) => {
+  let san = getX509Ext(x509.extensions, "2.5.29.17").parsedValue;
+  if (san && san.hasOwnProperty("altNames")) {
+    san = Object.keys(san.altNames).map(x => {
+      const type = san.altNames[x].type;
+
+      switch (type) {
+        case 4: // directory
+          return [
+            strings.san[type],
+            parseSubsidiary(san.altNames[x].value.typesAndValues).dn,
+          ];
+        case 7: // ip address
+          let address = san.altNames[x].value.valueBlock.valueHex;
+
+          if (address.length === 8) {
+            // ipv4
+            return [
+              strings.san[type],
+              address
+                .match(/.{1,2}/g)
+                .map(x => parseInt(x, 16))
+                .join("."),
+            ];
+          } else if (address.length === 32) {
+            // ipv6
+            return [
+              strings.san[type],
+              address
+                .toLowerCase()
+                .match(/.{1,4}/g)
+                .join(":")
+                .replace(/\b:?(?:0+:?){2,}/, "::"),
+            ];
+          }
+          return [strings.san[type], "Unknown IP address"];
+
+        default:
+          return [strings.san[type], san.altNames[x].value];
+      }
+    });
+  } else {
+    san = [];
+  }
+  san = {
+    altNames: san,
+    critical: criticalExtensions.includes("2.5.29.17"),
+  };
+  return san;
+};
+
+const getBasicConstraints = (x509, criticalExtensions) => {
+  let basicConstraints;
+  const basicConstraintsExt = getX509Ext(x509.extensions, "2.5.29.19");
+  if (basicConstraintsExt && basicConstraintsExt.parsedValue) {
+    basicConstraints = {
+      cA:
+        basicConstraintsExt.parsedValue.cA !== undefined &&
+        basicConstraintsExt.parsedValue.cA,
+      critical: criticalExtensions.includes("2.5.29.19"),
+    };
+  }
+  return basicConstraints;
+};
+
+const getEKeyUsages = (x509, criticalExtensions) => {
+  let eKeyUsages = getX509Ext(x509.extensions, "2.5.29.37").parsedValue;
+  if (eKeyUsages) {
+    eKeyUsages = {
+      critical: criticalExtensions.includes("2.5.29.37"),
+      purposes: eKeyUsages.keyPurposes.map(x => strings.eKU[x] || x),
+    };
+  }
+  return eKeyUsages;
+};
+
+const getSubjectKeyID = (x509, criticalExtensions) => {
+  let sKID = getX509Ext(x509.extensions, "2.5.29.14").parsedValue;
+  if (sKID) {
+    sKID = {
+      critical: criticalExtensions.includes("2.5.29.14"),
+      id: hashify(sKID.valueBlock.valueHex),
+    };
+  }
+  return sKID;
+};
+
+const getAuthorityKeyID = (x509, criticalExtensions) => {
+  let aKID = getX509Ext(x509.extensions, "2.5.29.35").parsedValue;
+  if (aKID) {
+    aKID = {
+      critical: criticalExtensions.includes("2.5.29.35"),
+      id: hashify(aKID.keyIdentifier.valueBlock.valueHex),
+    };
+  }
+  return aKID;
+};
+
+const getCRLPoints = (x509, criticalExtensions) => {
+  let crlPoints = getX509Ext(x509.extensions, "2.5.29.31").parsedValue;
+  if (crlPoints) {
+    crlPoints = {
+      critical: criticalExtensions.includes("2.5.29.31"),
+      points: crlPoints.distributionPoints.map(
+        x => x.distributionPoint[0].value
+      ),
+    };
+  }
+  return crlPoints;
+};
+
+const getOcspStaple = (x509, criticalExtensions) => {
+  let ocspStaple = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.24").extnValue;
+  if (ocspStaple && ocspStaple.valueBlock.valueHex === "3003020105") {
+    ocspStaple = {
+      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
+      required: true,
+    };
+  } else {
+    ocspStaple = {
+      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
+      required: false,
+    };
+  }
+  return ocspStaple;
+};
+
+const getAuthorityInfoAccess = (x509, criticalExtensions) => {
+  let aia = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.1").parsedValue;
+  if (aia) {
+    aia = aia.accessDescriptions.map(x => {
+      return {
+        location: x.accessLocation.value,
+        method: strings.aia[x.accessMethod],
+      };
+    });
+  }
+
+  aia = {
+    descriptions: aia,
+    critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.1"),
+  };
+  return aia;
+};
+
+const getSCTs = (x509, criticalExtensions) => {
+  let scts = getX509Ext(x509.extensions, "1.3.6.1.4.1.11129.2.4.2").parsedValue;
+  if (scts) {
+    scts = Object.keys(scts.timestamps).map(x => {
+      let logId = scts.timestamps[x].logID.toLowerCase();
+      return {
+        logId: hashify(logId),
+        name: ctLogNames.hasOwnProperty(logId) ? ctLogNames[logId] : undefined,
+        signatureAlgorithm: `${scts.timestamps[x].hashAlgorithm.replace(
+          "sha",
+          "SHA-"
+        )} ${scts.timestamps[x].signatureAlgorithm.toUpperCase()}`,
+        timestamp: `${scts.timestamps[
+          x
+        ].timestamp.toLocaleString()} (${getTimeZone()})`,
+        version: scts.timestamps[x].version + 1,
+      };
+    });
+  } else {
+    scts = [];
+  }
+
+  scts = {
+    critical: criticalExtensions.includes("1.3.6.1.4.1.11129.2.4.2"),
+    timestamps: scts,
+  };
+  return scts;
+};
+
+const getCertificatePolicies = (x509, criticalExtensions) => {
+  let cp = getX509Ext(x509.extensions, "2.5.29.32").parsedValue;
+  if (cp && cp.hasOwnProperty("certificatePolicies")) {
+    cp = cp.certificatePolicies.map(x => {
+      let id = x.policyIdentifier;
+      let name = strings.cps.hasOwnProperty(id)
+        ? strings.cps[id].name
+        : undefined;
+      let qualifiers = undefined;
+      let value = strings.cps.hasOwnProperty(id)
+        ? strings.cps[id].value
+        : undefined;
+
+      // ansi organization identifiers
+      if (id.startsWith("2.16.840.")) {
+        value = id;
+        id = "2.16.840";
+        name = strings.cps["2.16.840"].name;
+      }
+
+      // statement identifiers
+      if (id.startsWith("1.3.6.1.4.1")) {
+        value = id;
+        id = "1.3.6.1.4.1";
+        name = strings.cps["1.3.6.1.4.1"].name;
+      }
+
+      if (x.hasOwnProperty("policyQualifiers")) {
+        qualifiers = x.policyQualifiers.map(qualifier => {
+          let id = qualifier.policyQualifierId;
+          let name = strings.cps.hasOwnProperty(id)
+            ? strings.cps[id].name
+            : undefined;
+          let value = qualifier.qualifier.valueBlock.value;
+
+          // sometimes they are multiple qualifier subblocks, and for now we'll
+          // only return the first one because it's getting really messy at this point
+          if (Array.isArray(value) && value.length === 1) {
+            value = value[0].valueBlock.value;
+          } else if (Array.isArray(value) && value.length > 1) {
+            value = "(currently unsupported)";
+          }
+
+          return {
+            id,
+            name,
+            value,
+          };
+        });
+      }
+
+      return {
+        id,
+        name,
+        qualifiers,
+        value,
+      };
+    });
+  }
+
+  cp = {
+    critical: criticalExtensions.includes("2.5.29.32"),
+    policies: cp,
+  };
+  return cp;
+};
+
+const getMicrosoftCryptographicExtensions = (x509, criticalExtensions) => {
+  // now let's parse the Microsoft cryptographic extensions
+  let msCrypto = {
+    caVersion: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.1").parsedValue,
+    certificatePolicies: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.10")
+      .parsedValue,
+    certificateTemplate: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.7")
+      .parsedValue,
+    certificateType: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.20.2")
+      .parsedValue,
+    previousHash: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.2")
+      .parsedValue,
+  };
+
+  if (
+    msCrypto.caVersion &&
+    Number.isInteger(msCrypto.caVersion.keyIndex) &&
+    Number.isInteger(msCrypto.caVersion.certificateIndex)
+  ) {
+    msCrypto.caVersion = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.1"),
+      caRenewals: msCrypto.caVersion.certificateIndex,
+      keyReuses:
+        msCrypto.caVersion.certificateIndex - msCrypto.caVersion.keyIndex,
+    };
+  }
+
+  if (msCrypto.certificatePolicies) {
+    msCrypto.certificatePolicies = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.10"),
+      purposes: msCrypto.certificatePolicies.certificatePolicies.map(
+        x => strings.eKU[x.policyIdentifier] || x.policyIdentifier
+      ),
+    };
+  }
+
+  if (msCrypto.certificateTemplate) {
+    msCrypto.certificateTemplate = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.7"),
+      id: msCrypto.certificateTemplate.extnID,
+      major: msCrypto.certificateTemplate.templateMajorVersion,
+      minor: msCrypto.certificateTemplate.templateMinorVersion,
+    };
+  }
+
+  if (msCrypto.certificateType) {
+    msCrypto.certificateType = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.20.2"),
+      type:
+        strings.microsoftCertificateTypes[
+          msCrypto.certificateType.valueBlock.value
+        ] || "Unknown",
+    };
+  }
+
+  if (msCrypto.previousHash) {
+    msCrypto.previousHash = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.2"),
+      previousHash: hashify(msCrypto.previousHash.valueBlock.valueHex),
+    };
+  }
+
+  msCrypto.exists = !!(
+    msCrypto.caVersion ||
+    msCrypto.certificatePolicies ||
+    msCrypto.certificateTemplate ||
+    msCrypto.certificateType ||
+    msCrypto.previousHash
+  );
+
+  return msCrypto;
+};
+
+export const parse = async certificate => {
+  // certificate could be an array of BER or an array of buffers
+  const supportedExtensions = [
+    "1.3.6.1.4.1.311.20.2", // microsoft certificate type
+    "1.3.6.1.4.1.311.21.2", // microsoft certificate previous hash
+    "1.3.6.1.4.1.311.21.7", // microsoft certificate template
+    "1.3.6.1.4.1.311.21.1", // microsoft certification authority renewal
+    "1.3.6.1.4.1.311.21.10", // microsoft certificate policies
+    "1.3.6.1.4.1.11129.2.4.2", // embedded scts
+    "1.3.6.1.5.5.7.1.1", // authority info access
+    "1.3.6.1.5.5.7.1.24", // ocsp stapling
+    "1.3.101.77", // ct redaction - deprecated and not displayed
+    "2.5.29.14", // subject key identifier
+    "2.5.29.15", // key usages
+    "2.5.29.17", // subject alt names
+    "2.5.29.19", // basic constraints
+    "2.5.29.31", // crl points
+    "2.5.29.32", // certificate policies
+    "2.5.29.35", // authority key identifier
+    "2.5.29.37", // extended key usage
+  ];
+
+  let timeZone = getTimeZone();
+
+  // parse the certificate
+  const asn1 = fromBER(certificate);
+
+  let x509 = new Certificate({ schema: asn1.result });
+  x509 = x509.toJSON();
+
+  // convert the cert to PEM
+  const certBTOA = window
+    .btoa(String.fromCharCode.apply(null, new Uint8Array(certificate)))
+    .match(/.{1,64}/g)
+    .join("\r\n");
+
+  // get which extensions are critical
+  const criticalExtensions = [];
+  x509.extensions.forEach(ext => {
+    if (ext.hasOwnProperty("critical") && ext.critical === true) {
+      criticalExtensions.push(ext.extnID);
+    }
+  });
+
+  const spki = getPublicKeyInfo(x509);
+  const keyUsages = getKeyUsages(x509, criticalExtensions);
+  const san = getSubjectAltNames(x509, criticalExtensions);
+  const basicConstraints = getBasicConstraints(x509, criticalExtensions);
+  const eKeyUsages = getEKeyUsages(x509, criticalExtensions);
+  const sKID = getSubjectKeyID(x509, criticalExtensions);
+  const aKID = getAuthorityKeyID(x509, criticalExtensions);
+  const crlPoints = getCRLPoints(x509, criticalExtensions);
+  const ocspStaple = getOcspStaple(x509, criticalExtensions);
+  const aia = getAuthorityInfoAccess(x509, criticalExtensions);
+  const scts = getSCTs(x509, criticalExtensions);
+  const cp = getCertificatePolicies(x509, criticalExtensions);
+  const msCrypto = getMicrosoftCryptographicExtensions(
+    x509,
+    criticalExtensions
+  );
+
+  // determine which extensions weren't supported
+  let unsupportedExtensions = [];
+  x509.extensions.forEach(ext => {
+    if (!supportedExtensions.includes(ext.extnID)) {
+      unsupportedExtensions.push(ext.extnID);
+    }
+  });
+
+  // the output shell
+  return {
+    ext: {
+      aia,
+      aKID,
+      basicConstraints,
+      crlPoints,
+      cp,
+      eKeyUsages,
+      keyUsages,
+      msCrypto,
+      ocspStaple,
+      scts,
+      sKID,
+      san,
+    },
+    files: {
+      der: undefined, // TODO: implement!
+      pem: encodeURI(
+        `-----BEGIN CERTIFICATE-----\r\n${certBTOA}\r\n-----END CERTIFICATE-----\r\n`
+      ),
+    },
+    fingerprint: {
+      sha1: await hash("SHA-1", certificate),
+      sha256: await hash("SHA-256", certificate),
+    },
+    issuer: parseSubsidiary(x509.issuer.typesAndValues),
+    notBefore: `${x509.notBefore.value.toLocaleString()} (${timeZone})`,
+    notAfter: `${x509.notAfter.value.toLocaleString()} (${timeZone})`,
+    subject: parseSubsidiary(x509.subject.typesAndValues),
+    serialNumber: hashify(getObjPath(x509, "serialNumber.valueBlock.valueHex")),
+    signature: {
+      name: strings.signature[getObjPath(x509, "signature.algorithmId")],
+      type: getObjPath(x509, "signature.algorithmId"),
+    },
+    subjectPublicKeyInfo: spki,
+    unsupportedExtensions,
+    version: (x509.version + 1).toString(),
+  };
+};
--- a/toolkit/components/certviewer/content/certviewer.html
+++ b/toolkit/components/certviewer/content/certviewer.html
@@ -5,21 +5,24 @@
 <!DOCTYPE html>
 
 <html>
   <head>
     <meta name="viewport" content="width=device-width" />
     <meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
     <link rel="localization" href="toolkit/certviewer.ftl">
     <link rel="localization" href="branding/brand.ftl">
-    <script defer="defer" src="chrome://global/content/certviewer/certviewer.js"></script>
+    <script defer="defer" src="chrome://global/content/certviewer/pvutils_bundle.js"></script>
+    <script defer="defer" src="chrome://global/content/certviewer/asn1js_bundle.js"></script>
+    <script defer="defer" src="chrome://global/content/certviewer/pkijs_bundle.js"></script>
+    <script defer="defer" type="module" src="chrome://global/content/certviewer/certviewer.js"></script>
     <script defer="defer" src="chrome://global/content/certviewer/components/info-item.js"></script>
     <script defer="defer" src="chrome://global/content/certviewer/components/info-group.js"></script>
-    <script defer="defer" src="chrome://global/content/certviewer/components/dummy-info.js"></script>
-    <script defer="defer" src="chrome://global/content/certviewer/components/certificate-section.js"></script>
+    <script defer="defer" type="module" src="chrome://global/content/certviewer/components/dummy-info.js"></script>
+    <script defer="defer" type="module" src="chrome://global/content/certviewer/components/certificate-section.js"></script>
     <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
     <link rel="stylesheet" href="chrome://global/content/certviewer/certviewer.css">
     <title>about:certificate</title>
   </head>
   <body>
     <certificate-section></certificate-section> 
 
     <template id="certificate-section-template" class="section">
--- a/toolkit/components/certviewer/content/certviewer.js
+++ b/toolkit/components/certviewer/content/certviewer.js
@@ -1,27 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env mozilla/frame-script */
 
 "use strict";
 
+import { parse } from "chrome://global/content/certviewer/certDecoder.js";
+const { stringToArrayBuffer } = pvutils.pvutils;
+
+const derString =
+  '0\x82\x06F0\x82\x05.\xA0\x03\x02\x01\x02\x02\x10\f\x97n>B8\xF4 \xD6=\xDF\x86\xEF\xEB\xBA\x900\r\x06\t*\x86H\x86\xF7\r\x01\x01\v\x05\x000M1\v0\t\x06\x03U\x04\x06\x13\x02US1\x150\x13\x06\x03U\x04\n\x13\fDigiCert Inc1\'0%\x06\x03U\x04\x03\x13\x1EDigiCert SHA2 Secure Server CA0\x1E\x17\r181105000000Z\x17\r191113120000Z0\x81\x831\v0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\b\x13\nCalifornia1\x160\x14\x06\x03U\x04\x07\x13\rMountain View1\x1C0\x1A\x06\x03U\x04\n\x13\x13Mozilla Corporation1\x0F0\r\x06\x03U\x04\v\x13\x06WebOps1\x180\x16\x06\x03U\x04\x03\x13\x0Fwww.mozilla.org0\x82\x01"0\r\x06\t*\x86H\x86\xF7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0F\x000\x82\x01\n\x02\x82\x01\x01\x00\xB8\xAA\xEE\xCAi$\x9AJ\x82&\x1E\xD0\x8Ee\xE5P\xE0\\,Tr\xC3\x92\xC6\xFE\xF5\x14YZ\xEC\xC4-\xA0\xB1\xB4|X\x9A\xBEq\x8A\x18\x06\x9A\rU+J\xAC\xEE\x14\xDA\xB0a\xD4a\x1Bq\xCB\xD3\xEA\xA2\x8E@\x8F\xA9\x8E0\xF1\xC7\xD7&E\xDB\x9B\x191\xA9\xF0\xBD\f\x17Z!V\xF8H\xBD\x82\xEE\x98\xE1(D0\xCFS\x83\xEF\x18\x98\xC6\x85\xE9?\xA2;\xDEt\xF5\x9E\xF1\xD8\\\x88y18-\xAA<VoTS0\x83_\xE8\xFD\xBF4\xD7\x8E\xC1\xC1\x94&\xCC\x1D|\x9Aq\xEC\x99\xCC\x8A\x96\x9B\x027\xCAq\xC3\xCE\xAEj\x8FH\xBA\x7F e\n\xEC\x96U\xBA\xE2\xB4\xD9\x95\x14y\xEA\x91\xDD\x01\xCB\x86\x02\x86ca\x9CpK\xD6~\x96\xFA\xD2\x8CH+u\xD7\xA6[!\x86jZ\xB2\x16\x9Dk^UL\xD57\xF8\xFC\x86\x16\x01\x05\xD3\x815\r\xDFM\xEE\xDF\x13#\xB2\xCE\xD0+\xB7\x94\x0E\xC0\x02G\x18\x96<\xB5\xBD]\x00\xDD\xD5\xCF\xB2\xBD\xA6\t+8\t\xDB\x02\x03\x01\x00\x01\xA3\x82\x02\xE90\x82\x02\xE50\x1F\x06\x03U\x1D#\x04\x180\x16\x80\x14\x0F\x80a\x1C\x821a\xD5/(\xE7\x8DF8\xB4,\xE1\xC6\xD9\xE20\x1D\x06\x03U\x1D\x0E\x04\x16\x04\x14\xDAR\xBD!\x9C7eS\xFC\x1FSu\x0F\x1E_\x07\x9B\xA3\xAD?0\'\x06\x03U\x1D\x11\x04 0\x1E\x82\x0Fwww.mozilla.org\x82\vmozilla.org0\x0E\x06\x03U\x1D\x0F\x01\x01\xFF\x04\x04\x03\x02\x05\xA00\x1D\x06\x03U\x1D%\x04\x160\x14\x06\b+\x06\x01\x05\x05\x07\x03\x01\x06\b+\x06\x01\x05\x05\x07\x03\x020k\x06\x03U\x1D\x1F\x04d0b0/\xA0-\xA0+\x86)http://crl3.digicert.com/ssca-sha2-g6.crl0/\xA0-\xA0+\x86)http://crl4.digicert.com/ssca-sha2-g6.crl0L\x06\x03U\x1D \x04E0C07\x06\t`\x86H\x01\x86\xFDl\x01\x010*0(\x06\b+\x06\x01\x05\x05\x07\x02\x01\x16\x1Chttps://www.digicert.com/CPS0\b\x06\x06g\x81\f\x01\x02\x020|\x06\b+\x06\x01\x05\x05\x07\x01\x01\x04p0n0$\x06\b+\x06\x01\x05\x05\x070\x01\x86\x18http://ocsp.digicert.com0F\x06\b+\x06\x01\x05\x05\x070\x02\x86:http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt0\f\x06\x03U\x1D\x13\x01\x01\xFF\x04\x020\x000\x82\x01\x02\x06\n+\x06\x01\x04\x01\xD6y\x02\x04\x02\x04\x81\xF3\x04\x81\xF0\x00\xEE\x00u\x00\xA4\xB9\t\x90\xB4\x18X\x14\x87\xBB\x13\xA2\xCCgp\n<5\x98\x04\xF9\x1B\xDF\xB8\xE3w\xCD\x0E\xC8\r\xDC\x10\x00\x00\x01f\xE6\x16\x88|\x00\x00\x04\x03\x00F0D\x02 fs\x12\x1FR]\x1B\xA3@Hu\x93\xC0=&\x94\xFF\xF3n\xBD!\xCC\xFD\xBA\xDD\xCD6bm\x03S\xAE\x02 G\xB8@rC?\x8E\xE3\xD1\xBE\xA8L[\xBAF\xB6s\xD4\xD4=r\xAF\x00\xCFR\xA70\xA4nhP%\x00u\x00\x87u\xBF\xE7Y|\xF8\x8CC\x99_\xBD\xF3n\xFFV\x8DGV6\xFFJ\xB5`\xC1\xB4\xEA\xFF^\xA0\x83\x0F\x00\x00\x01f\xE6\x16\x89\x02\x00\x00\x04\x03\x00F0D\x02 1\x18\xB5\xE4Q\xA3\x80\x91\x98W5\xE3Q\xDE\x95\xB2j\x16^*d\x9A1v\x9D\x82\xED|I\xF2\xB5d\x02 *\xDC.\xE5\xE1\xB1+\xBE\xAB\x81\xAB3,&}y\xD0H\x8E\xE54\f\xAA+\xCC\xF5.\xC5E\xC5cD0\r\x06\t*\x86H\x86\xF7\r\x01\x01\v\x05\x00\x03\x82\x01\x01\x00\xA2\xC9\x00S\xB7\xC2\xE6\x8F\xE4\xC3?y\xDDe\x86NTsf\x83\xA2H\\3\xB5\xF2\xBD\xD8D)!\xDB\x80\xF8\\\x80\xCA\x13\xF5\x82\x15\xA0\xF6\xBB\xD2\x03B\xE8\xA1\xDC\xC2\x85\xEE\xD2\x0F0\xB7\xB5\xFAVmj\x97\xFE\xBC\x1B\x9A\xBC\xE3\x89\x05\xB8.\x89>^\vU?fr \xFEUn\x9B\x1DD\x97\x0F\xDCb|\xBFC\xA9\xD2t!\xCD\x12\x96T\xE53\xEA*\xE5\xA0\x1E;\x15\x10\x19xW"\xA8\xFA}$/\xF5\xF4\r\xEAe\xF1@\x8A\x1C\xFE\b\xF1\xB5\xEB$\xAB\xF5e\xF1\x89\x98\xD4R\x19?\xD0a\xEE\r\xBB\x93\x16}\x18\x00\x06B\xD8\xD3/M\xD1\xCC\xA0.J\x0E\xB2x\x89\x98U\xD4\x16j\xC7;P\xF9\xC1^T@\xCD\x9F>8\x94\xE1Q\xBC\xCA\xD7\xA6\xB6\b\x8D\xD1\x83\x86\xA0)"d\xFA\xE08\xEE\x1D\x7F"i\xA7\x82\x91KE\xBF\xCC\xBD\x15\xFA\x1FZ\x16qK\xB2\x1F\xA3\xBB=\x97e\xD1\x8ApN\x9C5Lr.\xA8!\xA5\xFF\nO\x83$ q3';
+
 let gElements = {};
 
 document.addEventListener("DOMContentLoaded", e => {
+  buildChain();
   gElements.certificateSection = document.querySelector("certificate-section");
 });
 
-const updateSelectedItem = (() => {
+export const updateSelectedItem = (() => {
   let state;
   return selectedItem => {
     if (selectedItem) {
       if (state !== selectedItem) {
         state = selectedItem;
         gElements.certificateSection.updateCertificateSource(selectedItem);
         gElements.certificateSection.updateSelectedTab(selectedItem);
       }
     }
     return state;
   };
 })();
+
+const buildChain = async () => {
+  let chain = [derString];
+  let builtChain = chain.map(cert => {
+    return stringToArrayBuffer(cert);
+  });
+  let certs = await Promise.all(builtChain.map(cert => parse(cert)));
+  console.log("certs ", certs);
+};
--- a/toolkit/components/certviewer/content/components/certificate-section.js
+++ b/toolkit/components/certviewer/content/components/certificate-section.js
@@ -1,13 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* globals certArray, updateSelectedItem, InfoGroup, ErrorSection */
+/* globals InfoGroup, ErrorSection */
+
+import { updateSelectedItem } from "chrome://global/content/certviewer/certviewer.js";
+import { certArray } from "chrome://global/content/certviewer/components/dummy-info.js";
 
 class CertificateSection extends HTMLElement {
   constructor() {
     super();
   }
 
   connectedCallback() {
     let template = document.getElementById("certificate-section-template");
--- a/toolkit/components/certviewer/content/components/dummy-info.js
+++ b/toolkit/components/certviewer/content/components/dummy-info.js
@@ -1,32 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const handshakeArray = [
-  {
-    label: "Protocol",
-    info: "TLS 1.2",
-  },
-  {
-    label: "Cipher Suite",
-    info: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
-  },
-  {
-    label: "Key Exchange Group",
-    info: "P256",
-  },
-  {
-    label: "Signature Scheme",
-    info: "RSA-PKCS1-SHA512",
-  },
-];
-
-const certArray = [
+export const certArray = [
   [
     {
       sectionTitle: "Subject Name",
       sectionItems: [
         {
           label: "Common Name",
           info: "developer.mozilla.org",
         },
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/ctlognames.js
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export const ctLogNames = {
+  "9606c02c690033aa1d145f59c6e2648d0549f0df96aab8db915a70d8ecf390a5":
+    "Akamai CT",
+  "39376f545f7b4607f59742d768cd5d2437bf3473b6534a4834bcf72e681c83c9":
+    "Alpha CT",
+  a577ac9ced7548dd8f025b67a241089df86e0f476ec203c2ecbedb185f282638: "CNNIC CT",
+  cdb5179b7fc1c046feea31136a3f8f002e6182faf8896fecc8b2f5b5ab604900: "Certly.IO",
+  "1fbc36e002ede97f40199e86b3573b8a4217d80187746ad0da03a06054d20df4":
+    "Cloudflare “Nimbus2017”",
+  db74afeecb29ecb1feca3e716d2ce5b9aabb36f7847183c75d9d4f37b61fbf64:
+    "Cloudflare “Nimbus2018”",
+  "747eda8331ad331091219cce254f4270c2bffd5e422008c6373579e6107bcc56":
+    "Cloudflare “Nimbus2019”",
+  "5ea773f9df56c0e7b536487dd049e0327a919a0c84a112128418759681714558":
+    "Cloudflare “Nimbus2020”",
+  "4494652eb0eeceafc44007d8a8fe28c0dae682bed8cb31b53fd33396b5b681a8":
+    "Cloudflare “Nimbus2021”",
+  "41c8cab1df22464a10c6a13a0942875e4e318b1b03ebeb4bc768f090629606f6":
+    "Cloudflare “Nimbus2022”",
+  "7a328c54d8b72db620ea38e0521ee98416703213854d3bd22bc13a57a352eb52":
+    "Cloudflare “Nimbus2023”",
+  "6ff141b5647e4222f7ef052cefae7c21fd608e27d2af5a6e9f4b8a37d6633ee5":
+    "DigiCert Nessie2018",
+  fe446108b1d01ab78a62ccfeab6ab2b2babff3abdad80a4d8b30df2d0008830c:
+    "DigiCert Nessie2019",
+  c652a0ec48ceb3fcab170992c43a87413309e80065a26252401ba3362a17c565:
+    "DigiCert Nessie2020",
+  eec095ee8d72640f92e3c3b91bc712a3696a097b4b6a1a1438e647b2cbedc5f9:
+    "DigiCert Nessie2021",
+  "51a3b0f5fd01799c566db837788f0ca47acc1b27cbf79e88429a0dfed48b05e5":
+    "DigiCert Nessie2022",
+  b3737707e18450f86386d605a9dc11094a792db1670c0b87dcf0030e7936a59a:
+    "DigiCert Nessie2023",
+  "5614069a2fd7c2ecd3f5e1bd44b23ec74676b9bc99115cc0ef949855d689d0dd":
+    "DigiCert Server",
+  "8775bfe7597cf88c43995fbdf36eff568d475636ff4ab560c1b4eaff5ea0830f":
+    "DigiCert Server 2",
+  c1164ae0a772d2d4392dc80ac10770d4f0c49bde991a4840c1fa075164f63360:
+    "DigiCert Yeti2018",
+  e2694bae26e8e94009e8861bb63b83d43ee7fe7488fba48f2893019dddf1dbfe:
+    "DigiCert Yeti2019",
+  f095a459f200d18240102d2f93888ead4bfe1d47e399e1d034a6b0a8aa8eb273:
+    "DigiCert Yeti2020",
+  "5cdc4392fee6ab4544b15e9ad456e61037fbd5fa47dca17394b25ee6f6c70eca":
+    "DigiCert Yeti2021",
+  "2245450759552456963fa12ff1f76d86e0232663adc04b7f5dc6835c6ee20f02":
+    "DigiCert Yeti2022",
+  "35cf191bbfb16c57bf0fad4c6d42cbbbb627202651ea3fe12aefa803c33bd64c":
+    "DigiCert Yeti2023",
+  "717ea7420975be84a2723553f1777c26dd51af4e102144094d9019b462fb6668": "GDCA 1",
+  "14308d90ccd030135005c01ca526d81e84e87624e39b6248e08f724aea3bb42a": "GDCA 2",
+  c9cf890a21109c666cc17a3ed065c930d0e0135a9feba85af14210b8072421aa:
+    "GDCA CT #1",
+  "924a30f909336ff435d6993a10ac75a2c641728e7fc2d659ae6188ffad40ce01":
+    "GDCA CT #2",
+  fad4c97cc49ee2f8ac85c5ea5cea09d0220dbbf4e49c6b50662ff868f86b8c28:
+    "Google “Argon2017”",
+  a4501269055a15545e6211ab37bc103f62ae5576a45e4b1714453e1b22106a25:
+    "Google “Argon2018”",
+  "63f2dbcde83bcc2ccf0b728427576b33a48d61778fbd75a638b1c768544bd88d":
+    "Google “Argon2019”",
+  b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e:
+    "Google “Argon2020”",
+  f65c942fd1773022145418083094568ee34d131933bfdf0c2f200bcc4ef164e3:
+    "Google “Argon2021”",
+  "2979bef09e393921f056739f63a577e5be577d9c600af8f94d5d265c255dc784":
+    "Google “Argon2022”",
+  "68f698f81f6482be3a8ceeb9281d4cfc71515d6793d444d10a67acbb4f4ffbc4":
+    "Google “Aviator”",
+  c3bf03a7e1ca8841c607bae3ff4270fca5ec45b186ebbe4e2cf3fc778630f5f6:
+    "Google “Crucible”",
+  "1d024b8eb1498b344dfd87ea3efc0996f7506f235d1d497061a4773c439c25fb":
+    "Google “Daedalus”",
+  "293c519654c83965baaa50fc5807d4b76fbf587a2972dca4c30cf4e54547f478":
+    "Google “Icarus”",
+  a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc10:
+    "Google “Pilot”",
+  ee4bbdb775ce60bae142691fabe19e66a30f7e5fb072d88300c47b897aa8fdcb:
+    "Google “Rocketeer”",
+  bbd9dfbc1f8a71b593942397aa927b473857950aab52e81a909664368e1ed185:
+    "Google “Skydiver”",
+  "52eb4b225ec896974850675f23e43bc1d021e3214ce52ecd5fa87c203cdfca03":
+    "Google “Solera2018”",
+  "0b760e9a8b9a682f88985b15e947501a56446bba8830785c3842994386450c00":
+    "Google “Solera2019”",
+  "1fc72ce5a1b799f400c359bff96ca3913548e8644220610952e9ba1774f7bac7":
+    "Google “Solera2020”",
+  a3c99845e80ab7ce00157b3742df0207dd272b2b602ecf98ee2c12db9c5ae7e7:
+    "Google “Solera2021”",
+  "697aafca1a6b536fae21205046debad7e0eaea13d2432e6e9d8fb379f2b9aaf3":
+    "Google “Solera2022”",
+  a899d8780c9290aaf462f31880ccfbd52451e970d0fbf591ef75b0d99b645681:
+    "Google “Submariner”",
+  b0cc83e5a5f97d6baf7c09cc284904872ac7e88b132c6350b7c6fd26e16c6c77:
+    "Google “Testtube”",
+  b10cd559a6d67846811f7df9a51532739ac48d703bea0323da5d38755bc0ad4e:
+    "Google “Xenon2018”",
+  "084114980071532c16190460bcfc47fdc2653afa292c72b37ff863ae29ccc9f0":
+    "Google “Xenon2019”",
+  "07b75c1be57d68fff1b0c61d2315c7bae6577c5794b76aeebc613a1a69d3a21c":
+    "Google “Xenon2020”",
+  "7d3ef2f88fff88556824c2c0ca9e5289792bc50e78097f2e6a9768997e22f0d7":
+    "Google “Xenon2021”",
+  "46a555eb75fa912030b5a28969f4f37d112c4174befd49b885abf2fc70fe6d47":
+    "Google “Xenon2022”",
+  "7461b4a09cfb3d41d75159575b2e7649a445a8d27709b0cc564a6482b7eb41a3": "Izenpe",
+  "8941449c70742e06b9fc9ce7b116ba0024aa36d59af44f0204404f00f7ea8566":
+    "Izenpe “Argi”",
+  "296afa2d568bca0d2ea844956ae9721fc35fa355ecda99693aafd458a71aefdd":
+    "Let“s Encrypt ”Clicky”",
+  "537b69a3564335a9c04904e39593b2c298eb8d7a6e83023635c627248cd6b440":
+    "Nordu “flimsy”",
+  aae70b7f3cb8d566c86c2f16979c9f445f69ab0eb4535589b2f77a030104f3cd:
+    "Nordu “plausible”",
+  e0127629e90496564e3d0147984498aa48f8adb16600eb7902a1ef9909906273:
+    "PuChuangSiDa CT",
+  cf55e28923497c340d5206d05353aeb25834b52f1f8dc9526809f212efdd7ca6:
+    "SHECA CT 1",
+  "32dc59c2d4c41968d56e14bc61ac8f0e45db39faf3c155aa4252f5001fa0c623":
+    "SHECA CT 2",
+  db76fdadac65e7d09508886e2159bd8b90352f5fead3e3dc5e22eb350acc7b98:
+    "Sectigo (Comodo) “Dodo” CT",
+  "6f5376ac31f03119d89900a45115ff77151c11d902c10029068db2089a37d913":
+    "Sectigo (Comodo) “Mammoth” CT",
+  "5581d4c2169036014aea0b9b573c53f0c0e43878702508172fa3aa1d0713d30c":
+    "Sectigo (Comodo) “Sabre” CT",
+  "34bb6ad6c3df9c03eea8a499ff7891486c9d5e5cac92d01f7bfd1bce19db48ef":
+    "StartCom",
+  ddeb1d2b7a0d4fa6208b81ad8168707e2e8e9d01d55c888d3d11c4cdb6ecbecc: "Symantec",
+  a7ce4a4e6207e0addee5fdaa4b1f86768767b5d002a55d47310e7e670a95eab2:
+    "Symantec Deneb",
+  "15970488d7b997a05beb52512adee8d2e8b4a3165264121a9fabfbd5f85ad93f":
+    "Symantec “Sirius”",
+  bc78e1dfc5f63c684649334da10fa15f0979692009c081b4f3f6917f3ed9b8a5:
+    "Symantec “Vega”",
+  b0b784bc81c0ddc47544e883f05985bb9077d134d8ab88b2b2e533980b8e508b:
+    "Up In The Air “Behind the Sofa”",
+  ac3b9aed7fa9674757159e6d7d575672f9d98100941e9bdeffeca1313b75782d: "Venafi",
+  "03019df3fd85a69a8ebd1facc6da9ba73e469774fe77f579fc5a08b8328c1d6b":
+    "Venafi Gen2 CT",
+  "41b2dc2e89e63ce4af1ba7bb29bf68c6dee6f9f1cc047e30dffae3b3ba259263": "WoSign",
+  "63d0006026dde10bb0601f452446965ee2b6ea2cd4fbc95ac866a550af9075b7":
+    "WoSign 2",
+  "9e4ff73dc3ce220b69217c899e468076abf8d78636d5ccfc85a31a75628ba88b":
+    "WoSign CT #1",
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/package.json
@@ -0,0 +1,20 @@
+{
+  "name": "certviewer",
+  "version": "1.0.0",
+  "description": "",
+  "scripts": {
+    "build-pvutils": "./node_modules/browserify/bin/cmd.js pvutils.js --standalone pvutils -o ./vendor/pvutils_bundle.js",
+    "build-asn1js": "./node_modules/browserify/bin/cmd.js asn1js.js --standalone asn1js -o ./vendor/asn1js_bundle.js",
+    "build-pkijs": "./node_modules/browserify/bin/cmd.js pkijs.js --standalone pkijs -o ./vendor/pkijs_bundle.js",
+    "build": "npm run build-pvutils && npm run build-asn1js && npm run build-pkijs"
+  },
+  "license": "MPL-2.0",
+  "dependencies": {
+    "asn1js": "^2.0.22",
+    "pkijs": "^2.1.78",
+    "pvutils": "^1.0.17"
+  },
+  "devDependencies": {
+    "browserify": "^16.2.3"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/pkijs.js
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const pkijs = require("pkijs"); // version 2.1.78
+
+module.exports = {
+  pkijs,
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/pvutils.js
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const pvutils = require("pvutils"); // version 1.0.17
+
+module.exports = {
+  pvutils,
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/strings.js
@@ -0,0 +1,504 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export const strings = {
+  ux: {
+    upload: "Upload Certificate",
+  },
+
+  names: {
+    // Directory Pilot Attributes
+    "0.9.2342.19200300.100.1.1": {
+      short: "uid",
+      long: "User ID",
+    },
+    "0.9.2342.19200300.100.1.25": {
+      short: "dc",
+      long: "Domain Component",
+    },
+
+    // PKCS-9
+    "1.2.840.113549.1.9.1": {
+      short: "e",
+      long: "Email Address",
+    },
+
+    // Incorporated Locations
+    "1.3.6.1.4.1.311.60.2.1.1": {
+      short: undefined,
+      long: "Inc. Locality",
+    },
+    "1.3.6.1.4.1.311.60.2.1.2": {
+      short: undefined,
+      long: "Inc. State / Province",
+    },
+    "1.3.6.1.4.1.311.60.2.1.3": {
+      short: undefined,
+      long: "Inc. Country",
+    },
+
+    // microsoft cryptographic extensions
+    "1.3.6.1.4.1.311.21.7": {
+      name: {
+        short: "Certificate Template",
+        long: "Microsoft Certificate Template",
+      },
+    },
+    "1.3.6.1.4.1.311.21.10": {
+      name: {
+        short: "Certificate Policies",
+        long: "Microsoft Certificate Policies",
+      },
+    },
+
+    // certificate extensions
+    "1.3.6.1.4.1.11129.2.4.2": {
+      name: {
+        short: "Embedded SCTs",
+        long: "Embedded Signed Certificate Timestamps",
+      },
+    },
+    "1.3.6.1.5.5.7.1.1": {
+      name: {
+        short: undefined,
+        long: "Authority Information Access",
+      },
+    },
+    "1.3.6.1.5.5.7.1.24": {
+      name: {
+        short: "OCSP Stapling",
+        long: "Online Certificate Status Protocol Stapling",
+      },
+    },
+
+    // X.500 attribute types
+    "2.5.4.1": {
+      short: undefined,
+      long: "Aliased Entry",
+    },
+    "2.5.4.2": {
+      short: undefined,
+      long: "Knowledge Information",
+    },
+    "2.5.4.3": {
+      short: "cn",
+      long: "Common Name",
+    },
+    "2.5.4.4": {
+      short: "sn",
+      long: "Surname",
+    },
+    "2.5.4.5": {
+      short: "serialNumber",
+      long: "Serial Number",
+    },
+    "2.5.4.6": {
+      short: "c",
+      long: "Country",
+    },
+    "2.5.4.7": {
+      short: "l",
+      long: "Locality",
+    },
+    "2.5.4.8": {
+      short: "s",
+      long: "State / Province",
+    },
+    "2.5.4.9": {
+      short: "street",
+      long: "Stress Address",
+    },
+    "2.5.4.10": {
+      short: "o",
+      long: "Organization",
+    },
+    "2.5.4.11": {
+      short: "ou",
+      long: "Organizational Unit",
+    },
+    "2.5.4.12": {
+      short: "t",
+      long: "Title",
+    },
+    "2.5.4.13": {
+      short: "description",
+      long: "Description",
+    },
+    "2.5.4.14": {
+      short: undefined,
+      long: "Search Guide",
+    },
+    "2.5.4.15": {
+      short: undefined,
+      long: "Business Category",
+    },
+    "2.5.4.16": {
+      short: undefined,
+      long: "Postal Address",
+    },
+    "2.5.4.17": {
+      short: "postalCode",
+      long: "Postal Code",
+    },
+    "2.5.4.18": {
+      short: "POBox",
+      long: "PO Box",
+    },
+    "2.5.4.19": {
+      short: undefined,
+      long: "Physical Delivery Office Name",
+    },
+    "2.5.4.20": {
+      short: "phone",
+      long: "Phone Number",
+    },
+    "2.5.4.21": {
+      short: undefined,
+      long: "Telex Number",
+    },
+    "2.5.4.22": {
+      short: undefined,
+      long: "Teletex Terminal Identifier",
+    },
+    "2.5.4.23": {
+      short: undefined,
+      long: "Fax Number",
+    },
+    "2.5.4.24": {
+      short: undefined,
+      long: "X.121 Address",
+    },
+    "2.5.4.25": {
+      short: undefined,
+      long: "International ISDN Number",
+    },
+    "2.5.4.26": {
+      short: undefined,
+      long: "Registered Address",
+    },
+    "2.5.4.27": {
+      short: undefined,
+      long: "Destination Indicator",
+    },
+    "2.5.4.28": {
+      short: undefined,
+      long: "Preferred Delivery Method",
+    },
+    "2.5.4.29": {
+      short: undefined,
+      long: "Presentation Address",
+    },
+    "2.5.4.30": {
+      short: undefined,
+      long: "Supported Application Context",
+    },
+    "2.5.4.31": {
+      short: undefined,
+      long: "Member",
+    },
+    "2.5.4.32": {
+      short: undefined,
+      long: "Owner",
+    },
+    "2.5.4.33": {
+      short: undefined,
+      long: "Role Occupant",
+    },
+    "2.5.4.34": {
+      short: undefined,
+      long: "See Also",
+    },
+    "2.5.4.35": {
+      short: undefined,
+      long: "User Password",
+    },
+    "2.5.4.36": {
+      short: undefined,
+      long: "User Certificate",
+    },
+    "2.5.4.37": {
+      short: undefined,
+      long: "CA Certificate",
+    },
+    "2.5.4.38": {
+      short: undefined,
+      long: "Authority Revocation List",
+    },
+    "2.5.4.39": {
+      short: undefined,
+      long: "Certificate Revocation List",
+    },
+    "2.5.4.40": {
+      short: undefined,
+      long: "Cross-certificate Pair",
+    },
+    "2.5.4.41": {
+      short: undefined,
+      long: "Name",
+    },
+    "2.5.4.42": {
+      short: "g",
+      long: "Given Name",
+    },
+    "2.5.4.43": {
+      short: "i",
+      long: "Initials",
+    },
+    "2.5.4.44": {
+      short: undefined,
+      long: "Generation Qualifier",
+    },
+    "2.5.4.45": {
+      short: undefined,
+      long: "Unique Identifier",
+    },
+    "2.5.4.46": {
+      short: undefined,
+      long: "DN Qualifier",
+    },
+    "2.5.4.47": {
+      short: undefined,
+      long: "Enhanced Search Guide",
+    },
+    "2.5.4.48": {
+      short: undefined,
+      long: "Protocol Information",
+    },
+    "2.5.4.49": {
+      short: "dn",
+      long: "Distinguished Name",
+    },
+    "2.5.4.50": {
+      short: undefined,
+      long: "Unique Member",
+    },
+    "2.5.4.51": {
+      short: undefined,
+      long: "House Identifier",
+    },
+    "2.5.4.52": {
+      short: undefined,
+      long: "Supported Algorithms",
+    },
+    "2.5.4.53": {
+      short: undefined,
+      long: "Delta Revocation List",
+    },
+    "2.5.4.58": {
+      short: undefined,
+      long: "Attribute Certificate Attribute", // huh
+    },
+    "2.5.4.65": {
+      short: undefined,
+      long: "Pseudonym",
+    },
+
+    // extensions
+    "2.5.29.14": {
+      name: {
+        short: "Subject Key ID",
+        long: "Subject Key Identifier",
+      },
+    },
+    "2.5.29.15": {
+      name: {
+        short: undefined,
+        long: "Key Usages",
+      },
+    },
+    "2.5.29.17": {
+      name: {
+        short: "Subject Alt Names",
+        long: "Subject Alternative Names",
+      },
+    },
+    "2.5.29.19": {
+      name: {
+        short: undefined,
+        long: "Basic Constraints",
+      },
+    },
+    "2.5.29.31": {
+      name: {
+        short: "CRL Endpoints",
+        long: "Certificate Revocation List Endpoints",
+      },
+    },
+    "2.5.29.32": {
+      name: {
+        short: undefined,
+        long: "Certificate Policies",
+      },
+    },
+    "2.5.29.35": {
+      name: {
+        short: "Authority Key ID",
+        long: "Authority Key Identifier",
+      },
+    },
+    "2.5.29.37": {
+      name: {
+        short: undefined,
+        long: "Extended Key Usages",
+      },
+    },
+  },
+
+  keyUsages: [
+    "CRL Signing",
+    "Certificate Signing",
+    "Key Agreement",
+    "Data Encipherment",
+    "Key Encipherment",
+    "Non-Repudiation",
+    "Digital Signature",
+  ],
+
+  san: [
+    "Other Name",
+    "RFC 822 Name",
+    "DNS Name",
+    "X.400 Address",
+    "Directory Name",
+    "EDI Party Name",
+    "URI",
+    "IP Address",
+    "Registered ID",
+  ],
+
+  eKU: {
+    "1.3.6.1.4.1.311.10.3.1": "Certificate Trust List (CTL) Signing",
+    "1.3.6.1.4.1.311.10.3.2": "Timestamp Signing",
+    "1.3.6.1.4.1.311.10.3.4": "EFS Encryption",
+    "1.3.6.1.4.1.311.10.3.4.1": "EFS Recovery",
+    "1.3.6.1.4.1.311.10.3.5":
+      "Windows Hardware Quality Labs (WHQL) Cryptography",
+    "1.3.6.1.4.1.311.10.3.7": "Windows NT 5 Cryptography",
+    "1.3.6.1.4.1.311.10.3.8": "Windows NT Embedded Cryptography",
+    "1.3.6.1.4.1.311.10.3.10": "Qualified Subordination",
+    "1.3.6.1.4.1.311.10.3.11": "Escrowed Key Recovery",
+    "1.3.6.1.4.1.311.10.3.12": "Document Signing",
+    "1.3.6.1.4.1.311.10.5.1": "Digital Rights Management",
+    "1.3.6.1.4.1.311.10.6.1": "Key Pack Licenses",
+    "1.3.6.1.4.1.311.10.6.2": "License Server",
+    "1.3.6.1.4.1.311.20.2.1": "Enrollment Agent",
+    "1.3.6.1.4.1.311.20.2.2": "Smartcard Login",
+    "1.3.6.1.4.1.311.21.5": "Certificate Authority Private Key Archival",
+    "1.3.6.1.4.1.311.21.6": "Key Recovery Agent",
+    "1.3.6.1.4.1.311.21.19": "Directory Service Email Replication",
+    "1.3.6.1.5.5.7.3.1": "Server Authentication",
+    "1.3.6.1.5.5.7.3.2": "Client Authentication",
+    "1.3.6.1.5.5.7.3.3": "Code Signing",
+    "1.3.6.1.5.5.7.3.4": "E-mail Protection",
+    "1.3.6.1.5.5.7.3.5": "IPsec End System",
+    "1.3.6.1.5.5.7.3.6": "IPsec Tunnel",
+    "1.3.6.1.5.5.7.3.7": "IPSec User",
+    "1.3.6.1.5.5.7.3.8": "Timestamping",
+    "1.3.6.1.5.5.7.3.9": "OCSP Signing",
+    "1.3.6.1.5.5.8.2.2": "Internet Key Exchange (IKE)",
+  },
+
+  signature: {
+    "1.2.840.113549.1.1.5": "SHA-1 with RSA Encryption",
+    "1.2.840.113549.1.1.11": "SHA-256 with RSA Encryption",
+    "1.2.840.113549.1.1.12": "SHA-384 with RSA Encryption",
+    "1.2.840.113549.1.1.13": "SHA-512 with RSA Encryption",
+    "1.2.840.10040.4.3": "DSA with SHA-1",
+    "2.16.840.1.101.3.4.3.2": "DSA with SHA-256",
+    "1.2.840.10045.4.1": "ECDSA with SHA-1",
+    "1.2.840.10045.4.3.2": "ECDSA with SHA-256",
+    "1.2.840.10045.4.3.3": "ECDSA with SHA-384",
+    "1.2.840.10045.4.3.4": "ECDSA with SHA-512",
+  },
+
+  aia: {
+    "1.3.6.1.5.5.7.48.1": "Online Certificate Status Protocol (OCSP)",
+    "1.3.6.1.5.5.7.48.2": "CA Issuers",
+  },
+
+  // this includes qualifiers as well
+  cps: {
+    "1.3.6.1.4.1": {
+      name: "Statement Identifier",
+      value: undefined,
+    },
+    "1.3.6.1.5.5.7.2.1": {
+      name: "Practices Statement",
+      value: undefined,
+    },
+    "1.3.6.1.5.5.7.2.2": {
+      name: "User Notice",
+      value: undefined,
+    },
+    "2.16.840": {
+      name: "ANSI Organizational Identifier",
+      value: undefined,
+    },
+    "2.23.140.1.1": {
+      name: "Certificate Type",
+      value: "Extended Validation",
+    },
+    "2.23.140.1.2.1": {
+      name: "Certificate Type",
+      value: "Domain Validation",
+    },
+    "2.23.140.1.2.2": {
+      name: "Certificate Type",
+      value: "Organization Validation",
+    },
+    "2.23.140.1.2.3": {
+      name: "Certificate Type",
+      value: "Individual Validation",
+    },
+    "2.23.140.1.3": {
+      name: "Certificate Type",
+      value: "Extended Validation (Code Signing)",
+    },
+    "2.23.140.1.31": {
+      name: "Certificate Type",
+      value: ".onion Extended Validation",
+    },
+    "2.23.140.2.1": {
+      name: "Certificate Type",
+      value: "Test Certificate",
+    },
+  },
+
+  microsoftCertificateTypes: {
+    Administrator: "Administrator",
+    CA: "Root Certification Authority",
+    CAExchange: "CA Exchange",
+    CEPEncryption: "CEP Encryption",
+    CertificateRequestAgent: "Certificate Request Agent",
+    ClientAuth: "Authenticated Session",
+    CodeSigning: "Code Signing",
+    CrossCA: "Cross Certification Authority",
+    CTLSigning: "Trust List Signing",
+    DirectoryEmailReplication: "Directory Email Replication",
+    DomainController: "Domain Controller",
+    DomainControllerAuthentication: "Domain Controller Authentication",
+    EFS: "Basic EFS",
+    EFSRecovery: "EFS Recovery Agent",
+    EnrollmentAgent: "Enrollment Agent",
+    EnrollmentAgentOffline: "Exchange Enrollment Agent (Offline request)",
+    ExchangeUser: "Exchange User",
+    ExchangeUserSignature: "Exchange Signature Only",
+    IPSECIntermediateOffline: "IPSec (Offline request)",
+    IPSECIntermediateOnline: "IPSEC",
+    KerberosAuthentication: "Kerberos Authentication",
+    KeyRecoveryAgent: "Key Recovery Agent",
+    Machine: "Computer",
+    MachineEnrollmentAgent: "Enrollment Agent (Computer)",
+    OCSPResponseSigning: "OCSP Response Signing",
+    OfflineRouter: "Router (Offline request)",
+    RASAndIASServer: "RAS and IAS Server",
+    SmartcardLogon: "Smartcard Logon",
+    SmartcardUser: "Smartcard User",
+    SubCA: "Subordinate Certification Authority",
+    User: "User",
+    UserSignature: "User Signature Only",
+    WebServer: "Web Server",
+    Workstation: "Workstation Authentication",
+  },
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/utils.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { Integer } = asn1js.asn1js;
+const { fromBase64, stringToArrayBuffer } = pvutils.pvutils;
+
+export const b64urltodec = b64 => {
+  return new Integer({
+    valueHex: stringToArrayBuffer(fromBase64("AQAB", true, true)),
+  }).valueBlock._valueDec;
+};
+
+export const b64urltohex = b64 => {
+  const hexBuffer = new Integer({
+    valueHex: stringToArrayBuffer(fromBase64(b64, true, true)),
+  }).valueBlock._valueHex;
+  const hexArray = Array.from(new Uint8Array(hexBuffer));
+
+  return hexArray.map(b => ("00" + b.toString(16)).slice(-2));
+};
+
+// this particular prototype override makes it easy to chain down complex objects
+export const getObjPath = (obj, path) => {
+  path = path.split(".");
+  for (let i = 0, len = path.length; i < len; i++) {
+    if (Array.isArray(obj[path[i]])) {
+      obj = obj[path[i]][path[i + 1]];
+      i++;
+    } else {
+      obj = obj[path[i]];
+    }
+  }
+  return obj;
+};
+
+export const hash = async (algo, buffer) => {
+  const hashBuffer = await crypto.subtle.digest(algo, buffer);
+  const hashArray = Array.from(new Uint8Array(hashBuffer));
+
+  return hashArray
+    .map(b => ("00" + b.toString(16)).slice(-2))
+    .join(":")
+    .toUpperCase();
+};
+
+export const hashify = hash => {
+  if (typeof hash === "string") {
+    return hash
+      .match(/.{2}/g)
+      .join(":")
+      .toUpperCase();
+  }
+  return hash.join(":").toUpperCase();
+};
+
+export const pemToDER = pem => {
+  return stringToArrayBuffer(window.atob(pem));
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/vendor/asn1js_bundle.js
@@ -0,0 +1,5877 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.asn1js = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ const asn1js = require("asn1js"); // version 2.0.22
+ 
+ module.exports = {
+   asn1js,
+ };
+ 
+},{"asn1js":2}],2:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+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.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 = undefined;
+exports.fromBER = fromBER;
+exports.compareSchema = compareSchema;
+exports.verifySchema = verifySchema;
+exports.fromJSON = fromJSON;
+
+var _pvutils = require("pvutils");
+
+//**************************************************************************************
+//region Declaration of global variables
+//**************************************************************************************
+const powers2 = [new Uint8Array([1])]; /* 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.
+ *
+ */
+//**************************************************************************************
+
+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 "LocalHexBlock" class
+//**************************************************************************************
+/**
+ * Class used as a base block for all remaining ASN.1 classes
+ * @extends LocalBaseBlock
+ * @typedef LocalHexBlock
+ * @property {number} blockLength
+ * @property {string} error
+ * @property {Array.<string>} warnings
+ * @property {ArrayBuffer} valueBeforeDecode
+ * @property {boolean} isHexOnly
+ * @property {ArrayBuffer} valueHex
+ */
+//noinspection JSUnusedLocalSymbols
+const LocalHexBlock = BaseClass => class LocalHexBlockMixin extends BaseClass {
+	//**********************************************************************************
+	//noinspection JSUnusedGlobalSymbols
+	/**
+  * Constructor for "LocalHexBlock" 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
+//**************************************************************************************
+class LocalIdentificationBlock extends LocalHexBlock(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 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 LocalValueBlock extends LocalBaseBlock {
+	//**********************************************************************************
+	/**
+  * Constructor for "LocalValueBlock" 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 \"LocalValueBlock\"");
+		//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 \"LocalValueBlock\"");
+		//endregion
+	}
+	//**********************************************************************************
+}
+//**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of basic ASN.1 block class
+//**************************************************************************************
+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 = LocalValueBlock) {
+		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;
+	}
+	//**********************************************************************************
+}
+exports.BaseBlock = BaseBlock; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of basic block for all PRIMITIVE types
+//**************************************************************************************
+
+class LocalPrimitiveValueBlock extends LocalValueBlock {
+	//**********************************************************************************
+	/**
+  * 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";
+	}
+	//**********************************************************************************
+}
+exports.Primitive = Primitive; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of basic block for all CONSTRUCTED types
+//**************************************************************************************
+
+class LocalConstructedValueBlock extends LocalValueBlock {
+	//**********************************************************************************
+	/**
+  * 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;
+	}
+	//**********************************************************************************
+}
+exports.Constructed = Constructed; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 EndOfContent type class
+//**************************************************************************************
+
+class LocalEndOfContentValueBlock extends LocalValueBlock {
+	//**********************************************************************************
+	/**
+  * 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";
+	}
+	//**********************************************************************************
+}
+exports.EndOfContent = EndOfContent; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 Boolean type class
+//**************************************************************************************
+
+class LocalBooleanValueBlock extends LocalValueBlock {
+	//**********************************************************************************
+	/**
+  * 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";
+	}
+	//**********************************************************************************
+}
+exports.Boolean = Boolean; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 Sequence and Set type classes
+//**************************************************************************************
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.Set = Set; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 Null type class
+//**************************************************************************************
+
+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;
+	}
+	//**********************************************************************************
+}
+exports.Null = Null; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 OctetString type class
+//**************************************************************************************
+
+class LocalOctetStringValueBlock extends LocalHexBlock(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;
+	}
+	//**********************************************************************************
+}
+exports.OctetString = OctetString; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 BitString type class
+//**************************************************************************************
+
+class LocalBitStringValueBlock extends LocalHexBlock(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;
+	}
+	//**********************************************************************************
+}
+exports.BitString = BitString; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 Integer type class
+//**************************************************************************************
+/**
+ * @extends LocalValueBlock
+ */
+
+class LocalIntegerValueBlock extends LocalHexBlock(LocalValueBlock) {
+	//**********************************************************************************
+	/**
+  * 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;
+	}
+	//**********************************************************************************
+}
+exports.Integer = Integer; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 Enumerated type class
+//**************************************************************************************
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.Enumerated = Enumerated; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of ASN.1 ObjectIdentifier type class
+//**************************************************************************************
+
+class LocalSidValueBlock extends LocalHexBlock(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 LocalValueBlock {
+	//**********************************************************************************
+	/**
+  * 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";
+	}
+	//**********************************************************************************
+}
+exports.ObjectIdentifier = ObjectIdentifier; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of all string's classes
+//**************************************************************************************
+
+class LocalUtf8StringValueBlock extends LocalHexBlock(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;
+	}
+	//**********************************************************************************
+}
+exports.Utf8String = Utf8String; //**************************************************************************************
+/**
+ * @extends LocalBaseBlock
+ * @extends LocalHexBlock
+ */
+
+class LocalBmpStringValueBlock extends LocalHexBlock(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 LocalHexBlock(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 LocalHexBlock(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";
+	}
+	//**********************************************************************************
+}
+exports.NumericString = NumericString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.PrintableString = PrintableString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.TeletexString = TeletexString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.VideotexString = VideotexString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.IA5String = IA5String; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.GraphicString = GraphicString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.VisibleString = VisibleString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.GeneralString = GeneralString; //**************************************************************************************
+/**
+ * @extends LocalSimpleStringBlock
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.CharacterString = CharacterString; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of all date and time classes
+//**************************************************************************************
+/**
+ * @extends VisibleString
+ */
+
+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;
+	}
+	//**********************************************************************************
+}
+exports.UTCTime = UTCTime; //**************************************************************************************
+/**
+ * @extends VisibleString
+ */
+
+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;
+	}
+	//**********************************************************************************
+}
+exports.GeneralizedTime = GeneralizedTime; //**************************************************************************************
+/**
+ * @extends Utf8String
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.DATE = DATE; //**************************************************************************************
+/**
+ * @extends Utf8String
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.TimeOfDay = TimeOfDay; //**************************************************************************************
+/**
+ * @extends Utf8String
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.DateTime = DateTime; //**************************************************************************************
+/**
+ * @extends Utf8String
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.Duration = Duration; //**************************************************************************************
+/**
+ * @extends Utf8String
+ */
+
+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";
+	}
+	//**********************************************************************************
+}
+exports.TIME = TIME; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of special ASN.1 schema type Choice
+//**************************************************************************************
+
+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);
+	}
+	//**********************************************************************************
+}
+exports.Choice = Choice; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of special ASN.1 schema type Any
+//**************************************************************************************
+
+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);
+	}
+	//**********************************************************************************
+}
+exports.Any = Any; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of special ASN.1 schema type Repeated
+//**************************************************************************************
+
+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
+	}
+	//**********************************************************************************
+}
+exports.Repeated = Repeated; //**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of special ASN.1 schema type RawData
+//**************************************************************************************
+/**
+ * @description Special class providing ability to have "toBER/fromBER" for raw ArrayBuffer
+ */
+
+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;
+	}
+	//**********************************************************************************
+}
+exports.RawData = RawData; //**************************************************************************************
+//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}}
+ */
+
+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
+	if ((0, _pvutils.checkBufferParams)(new LocalBaseBlock(), inputBuffer, inputOffset, inputLength) === false) {
+		returnObject.error = "Wrong input parameters";
+		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
+				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
+//**************************************************************************************
+
+},{"pvutils":3}],3:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+exports.getUTCDate = getUTCDate;
+exports.getParametersValue = getParametersValue;
+exports.bufferToHexCodes = bufferToHexCodes;
+exports.checkBufferParams = checkBufferParams;
+exports.utilFromBase = utilFromBase;
+exports.utilToBase = utilToBase;
+exports.utilConcatBuf = utilConcatBuf;
+exports.utilConcatView = utilConcatView;
+exports.utilDecodeTC = utilDecodeTC;
+exports.utilEncodeTC = utilEncodeTC;
+exports.isEqualBuffer = isEqualBuffer;
+exports.padNumber = padNumber;
+exports.toBase64 = toBase64;
+exports.fromBase64 = fromBase64;
+exports.arrayBufferToString = arrayBufferToString;
+exports.stringToArrayBuffer = stringToArrayBuffer;
+exports.nearestPowerOf2 = nearestPowerOf2;
+exports.clearProps = clearProps;
+//**************************************************************************************
+/**
+ * Making UTC date from local date
+ * @param {Date} date Date to convert from
+ * @returns {Date}
+ */
+function getUTCDate(date) {
+	// noinspection NestedFunctionCallJS, MagicNumberJS
+	return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleReturnPointsJS
+/**
+ * Get value for input parameters, or set a default value
+ * @param {Object} parameters
+ * @param {string} name
+ * @param defaultValue
+ */
+function getParametersValue(parameters, name, defaultValue) {
+	// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS
+	if (parameters instanceof Object === false) return defaultValue;
+
+	// noinspection NonBlockStatementBodyJS
+	if (name in parameters) return parameters[name];
+
+	return defaultValue;
+}
+//**************************************************************************************
+/**
+ * Converts "ArrayBuffer" into a hexdecimal string
+ * @param {ArrayBuffer} inputBuffer
+ * @param {number} [inputOffset=0]
+ * @param {number} [inputLength=inputBuffer.byteLength]
+ * @param {boolean} [insertSpace=false]
+ * @returns {string}
+ */
+function bufferToHexCodes(inputBuffer, inputOffset = 0, inputLength = inputBuffer.byteLength - inputOffset, insertSpace = false) {
+	let result = "";
+
+	var _iteratorNormalCompletion = true;
+	var _didIteratorError = false;
+	var _iteratorError = undefined;
+
+	try {
+		for (var _iterator = new Uint8Array(inputBuffer, inputOffset, inputLength)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+			const item = _step.value;
+
+			// noinspection ChainedFunctionCallJS
+			const str = item.toString(16).toUpperCase();
+
+			// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS
+			if (str.length === 1) result += "0";
+
+			result += str;
+
+			// noinspection NonBlockStatementBodyJS
+			if (insertSpace) result += " ";
+		}
+	} catch (err) {
+		_didIteratorError = true;
+		_iteratorError = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion && _iterator.return) {
+				_iterator.return();
+			}
+		} finally {
+			if (_didIteratorError) {
+				throw _iteratorError;
+			}
+		}
+	}
+
+	return result.trim();
+}
+//**************************************************************************************
+// noinspection JSValidateJSDoc, FunctionWithMultipleReturnPointsJS
+/**
+ * Check input "ArrayBuffer" for common functions
+ * @param {LocalBaseBlock} baseBlock
+ * @param {ArrayBuffer} inputBuffer
+ * @param {number} inputOffset
+ * @param {number} inputLength
+ * @returns {boolean}
+ */
+function checkBufferParams(baseBlock, inputBuffer, inputOffset, inputLength) {
+	// noinspection ConstantOnRightSideOfComparisonJS
+	if (inputBuffer instanceof ArrayBuffer === false) {
+		// noinspection JSUndefinedPropertyAssignment
+		baseBlock.error = "Wrong parameter: inputBuffer must be \"ArrayBuffer\"";
+		return false;
+	}
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	if (inputBuffer.byteLength === 0) {
+		// noinspection JSUndefinedPropertyAssignment
+		baseBlock.error = "Wrong parameter: inputBuffer has zero length";
+		return false;
+	}
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	if (inputOffset < 0) {
+		// noinspection JSUndefinedPropertyAssignment
+		baseBlock.error = "Wrong parameter: inputOffset less than zero";
+		return false;
+	}
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	if (inputLength < 0) {
+		// noinspection JSUndefinedPropertyAssignment
+		baseBlock.error = "Wrong parameter: inputLength less than zero";
+		return false;
+	}
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	if (inputBuffer.byteLength - inputOffset - inputLength < 0) {
+		// noinspection JSUndefinedPropertyAssignment
+		baseBlock.error = "End of input reached before message was fully decoded (inconsistent offset and length values)";
+		return false;
+	}
+
+	return true;
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleReturnPointsJS
+/**
+ * Convert number from 2^base to 2^10
+ * @param {Uint8Array} inputBuffer
+ * @param {number} inputBase
+ * @returns {number}
+ */
+function utilFromBase(inputBuffer, inputBase) {
+	let result = 0;
+
+	// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS
+	if (inputBuffer.length === 1) return inputBuffer[0];
+
+	// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS
+	for (let i = inputBuffer.length - 1; i >= 0; i--) result += inputBuffer[inputBuffer.length - 1 - i] * Math.pow(2, inputBase * i);
+
+	return result;
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS
+/**
+ * Convert number from 2^10 to 2^base
+ * @param {!number} value The number to convert
+ * @param {!number} base The base for 2^base
+ * @param {number} [reserved=0] Pre-defined number of bytes in output array (-1 = limited by function itself)
+ * @returns {ArrayBuffer}
+ */
+function utilToBase(value, base, reserved = -1) {
+	const internalReserved = reserved;
+	let internalValue = value;
+
+	let result = 0;
+	let biggest = Math.pow(2, base);
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	for (let i = 1; i < 8; i++) {
+		if (value < biggest) {
+			let retBuf;
+
+			// noinspection ConstantOnRightSideOfComparisonJS
+			if (internalReserved < 0) {
+				retBuf = new ArrayBuffer(i);
+				result = i;
+			} else {
+				// noinspection NonBlockStatementBodyJS
+				if (internalReserved < i) return new ArrayBuffer(0);
+
+				retBuf = new ArrayBuffer(internalReserved);
+
+				result = internalReserved;
+			}
+
+			const retView = new Uint8Array(retBuf);
+
+			// noinspection ConstantOnRightSideOfComparisonJS
+			for (let j = i - 1; j >= 0; j--) {
+				const basis = Math.pow(2, j * base);
+
+				retView[result - j - 1] = Math.floor(internalValue / basis);
+				internalValue -= retView[result - j - 1] * basis;
+			}
+
+			return retBuf;
+		}
+
+		biggest *= Math.pow(2, base);
+	}
+
+	return new ArrayBuffer(0);
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleLoopsJS
+/**
+ * Concatenate two ArrayBuffers
+ * @param {...ArrayBuffer} buffers Set of ArrayBuffer
+ */
+function utilConcatBuf(...buffers) {
+	//region Initial variables
+	let outputLength = 0;
+	let prevLength = 0;
+	//endregion
+
+	//region Calculate output length
+
+	// noinspection NonBlockStatementBodyJS
+	var _iteratorNormalCompletion2 = true;
+	var _didIteratorError2 = false;
+	var _iteratorError2 = undefined;
+
+	try {
+		for (var _iterator2 = buffers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+			const buffer = _step2.value;
+
+			outputLength += buffer.byteLength;
+		} //endregion
+	} catch (err) {
+		_didIteratorError2 = true;
+		_iteratorError2 = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion2 && _iterator2.return) {
+				_iterator2.return();
+			}
+		} finally {
+			if (_didIteratorError2) {
+				throw _iteratorError2;
+			}
+		}
+	}
+
+	const retBuf = new ArrayBuffer(outputLength);
+	const retView = new Uint8Array(retBuf);
+
+	var _iteratorNormalCompletion3 = true;
+	var _didIteratorError3 = false;
+	var _iteratorError3 = undefined;
+
+	try {
+		for (var _iterator3 = buffers[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+			const buffer = _step3.value;
+
+			// noinspection NestedFunctionCallJS
+			retView.set(new Uint8Array(buffer), prevLength);
+			prevLength += buffer.byteLength;
+		}
+	} catch (err) {
+		_didIteratorError3 = true;
+		_iteratorError3 = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion3 && _iterator3.return) {
+				_iterator3.return();
+			}
+		} finally {
+			if (_didIteratorError3) {
+				throw _iteratorError3;
+			}
+		}
+	}
+
+	return retBuf;
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleLoopsJS
+/**
+ * Concatenate two Uint8Array
+ * @param {...Uint8Array} views Set of Uint8Array
+ */
+function utilConcatView(...views) {
+	//region Initial variables
+	let outputLength = 0;
+	let prevLength = 0;
+	//endregion
+
+	//region Calculate output length
+	// noinspection NonBlockStatementBodyJS
+	var _iteratorNormalCompletion4 = true;
+	var _didIteratorError4 = false;
+	var _iteratorError4 = undefined;
+
+	try {
+		for (var _iterator4 = views[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+			const view = _step4.value;
+
+			outputLength += view.length;
+		} //endregion
+	} catch (err) {
+		_didIteratorError4 = true;
+		_iteratorError4 = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion4 && _iterator4.return) {
+				_iterator4.return();
+			}
+		} finally {
+			if (_didIteratorError4) {
+				throw _iteratorError4;
+			}
+		}
+	}
+
+	const retBuf = new ArrayBuffer(outputLength);
+	const retView = new Uint8Array(retBuf);
+
+	var _iteratorNormalCompletion5 = true;
+	var _didIteratorError5 = false;
+	var _iteratorError5 = undefined;
+
+	try {
+		for (var _iterator5 = views[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
+			const view = _step5.value;
+
+			retView.set(view, prevLength);
+			prevLength += view.length;
+		}
+	} catch (err) {
+		_didIteratorError5 = true;
+		_iteratorError5 = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion5 && _iterator5.return) {
+				_iterator5.return();
+			}
+		} finally {
+			if (_didIteratorError5) {
+				throw _iteratorError5;
+			}
+		}
+	}
+
+	return retView;
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleLoopsJS
+/**
+ * Decoding of "two complement" values
+ * The function must be called in scope of instance of "hexBlock" class ("valueHex" and "warnings" properties must be present)
+ * @returns {number}
+ */
+function utilDecodeTC() {
+	const buf = new Uint8Array(this.valueHex);
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	if (this.valueHex.byteLength >= 2) {
+		//noinspection JSBitwiseOperatorUsage, ConstantOnRightSideOfComparisonJS, LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		const condition1 = buf[0] === 0xFF && buf[1] & 0x80;
+		// noinspection ConstantOnRightSideOfComparisonJS, LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		const condition2 = buf[0] === 0x00 && (buf[1] & 0x80) === 0x00;
+
+		// noinspection NonBlockStatementBodyJS
+		if (condition1 || condition2) this.warnings.push("Needlessly long format");
+	}
+
+	//region Create big part of the integer
+	const bigIntBuffer = new ArrayBuffer(this.valueHex.byteLength);
+	const bigIntView = new Uint8Array(bigIntBuffer);
+	// noinspection NonBlockStatementBodyJS
+	for (let i = 0; i < this.valueHex.byteLength; i++) bigIntView[i] = 0;
+
+	// noinspection MagicNumberJS, NonShortCircuitBooleanExpressionJS
+	bigIntView[0] = buf[0] & 0x80; // mask only the biggest bit
+
+	const bigInt = utilFromBase(bigIntView, 8);
+	//endregion
+
+	//region Create small part of the integer
+	const smallIntBuffer = new ArrayBuffer(this.valueHex.byteLength);
+	const smallIntView = new Uint8Array(smallIntBuffer);
+	// noinspection NonBlockStatementBodyJS
+	for (let j = 0; j < this.valueHex.byteLength; j++) smallIntView[j] = buf[j];
+
+	// noinspection MagicNumberJS
+	smallIntView[0] &= 0x7F; // mask biggest bit
+
+	const smallInt = utilFromBase(smallIntView, 8);
+	//endregion
+
+	return smallInt - bigInt;
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS
+/**
+ * Encode integer value to "two complement" format
+ * @param {number} value Value to encode
+ * @returns {ArrayBuffer}
+ */
+function utilEncodeTC(value) {
+	// noinspection ConstantOnRightSideOfComparisonJS, ConditionalExpressionJS
+	const modValue = value < 0 ? value * -1 : value;
+	let bigInt = 128;
+
+	// noinspection ConstantOnRightSideOfComparisonJS
+	for (let i = 1; i < 8; i++) {
+		if (modValue <= bigInt) {
+			// noinspection ConstantOnRightSideOfComparisonJS
+			if (value < 0) {
+				const smallInt = bigInt - modValue;
+
+				const retBuf = utilToBase(smallInt, 8, i);
+				const retView = new Uint8Array(retBuf);
+
+				// noinspection MagicNumberJS
+				retView[0] |= 0x80;
+
+				return retBuf;
+			}
+
+			let retBuf = utilToBase(modValue, 8, i);
+			let retView = new Uint8Array(retBuf);
+
+			//noinspection JSBitwiseOperatorUsage, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+			if (retView[0] & 0x80) {
+				//noinspection JSCheckFunctionSignatures
+				const tempBuf = retBuf.slice(0);
+				const tempView = new Uint8Array(tempBuf);
+
+				retBuf = new ArrayBuffer(retBuf.byteLength + 1);
+				// noinspection ReuseOfLocalVariableJS
+				retView = new Uint8Array(retBuf);
+
+				// noinspection NonBlockStatementBodyJS
+				for (let k = 0; k < tempBuf.byteLength; k++) retView[k + 1] = tempView[k];
+
+				// noinspection MagicNumberJS
+				retView[0] = 0x00;
+			}
+
+			return retBuf;
+		}
+
+		bigInt *= Math.pow(2, 8);
+	}
+
+	return new ArrayBuffer(0);
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleReturnPointsJS, ParameterNamingConventionJS
+/**
+ * Compare two array buffers
+ * @param {!ArrayBuffer} inputBuffer1
+ * @param {!ArrayBuffer} inputBuffer2
+ * @returns {boolean}
+ */
+function isEqualBuffer(inputBuffer1, inputBuffer2) {
+	// noinspection NonBlockStatementBodyJS
+	if (inputBuffer1.byteLength !== inputBuffer2.byteLength) return false;
+
+	// noinspection LocalVariableNamingConventionJS
+	const view1 = new Uint8Array(inputBuffer1);
+	// noinspection LocalVariableNamingConventionJS
+	const view2 = new Uint8Array(inputBuffer2);
+
+	for (let i = 0; i < view1.length; i++) {
+		// noinspection NonBlockStatementBodyJS
+		if (view1[i] !== view2[i]) return false;
+	}
+
+	return true;
+}
+//**************************************************************************************
+// noinspection FunctionWithMultipleReturnPointsJS
+/**
+ * Pad input number with leade "0" if needed
+ * @returns {string}
+ * @param {number} inputNumber
+ * @param {number} fullLength
+ */
+function padNumber(inputNumber, fullLength) {
+	const str = inputNumber.toString(10);
+
+	// noinspection NonBlockStatementBodyJS
+	if (fullLength < str.length) return "";
+
+	const dif = fullLength - str.length;
+
+	const padding = new Array(dif);
+	// noinspection NonBlockStatementBodyJS
+	for (let i = 0; i < dif; i++) padding[i] = "0";
+
+	const paddingString = padding.join("");
+
+	return paddingString.concat(str);
+}
+//**************************************************************************************
+const base64Template = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+const base64UrlTemplate = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
+//**************************************************************************************
+// noinspection FunctionWithMultipleLoopsJS, OverlyComplexFunctionJS, FunctionTooLongJS, FunctionNamingConventionJS
+/**
+ * Encode string into BASE64 (or "base64url")
+ * @param {string} input
+ * @param {boolean} useUrlTemplate If "true" then output would be encoded using "base64url"
+ * @param {boolean} skipPadding Skip BASE-64 padding or not
+ * @param {boolean} skipLeadingZeros Skip leading zeros in input data or not
+ * @returns {string}
+ */
+function toBase64(input, useUrlTemplate = false, skipPadding = false, skipLeadingZeros = false) {
+	let i = 0;
+
+	// noinspection LocalVariableNamingConventionJS
+	let flag1 = 0;
+	// noinspection LocalVariableNamingConventionJS
+	let flag2 = 0;
+
+	let output = "";
+
+	// noinspection ConditionalExpressionJS
+	const template = useUrlTemplate ? base64UrlTemplate : base64Template;
+
+	if (skipLeadingZeros) {
+		let nonZeroPosition = 0;
+
+		for (let i = 0; i < input.length; i++) {
+			// noinspection ConstantOnRightSideOfComparisonJS
+			if (input.charCodeAt(i) !== 0) {
+				nonZeroPosition = i;
+				// noinspection BreakStatementJS
+				break;
+			}
+		}
+
+		// noinspection AssignmentToFunctionParameterJS
+		input = input.slice(nonZeroPosition);
+	}
+
+	while (i < input.length) {
+		// noinspection LocalVariableNamingConventionJS, IncrementDecrementResultUsedJS
+		const chr1 = input.charCodeAt(i++);
+		// noinspection NonBlockStatementBodyJS
+		if (i >= input.length) flag1 = 1;
+		// noinspection LocalVariableNamingConventionJS, IncrementDecrementResultUsedJS
+		const chr2 = input.charCodeAt(i++);
+		// noinspection NonBlockStatementBodyJS
+		if (i >= input.length) flag2 = 1;
+		// noinspection LocalVariableNamingConventionJS, IncrementDecrementResultUsedJS
+		const chr3 = input.charCodeAt(i++);
+
+		// noinspection LocalVariableNamingConventionJS
+		const enc1 = chr1 >> 2;
+		// noinspection LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		const enc2 = (chr1 & 0x03) << 4 | chr2 >> 4;
+		// noinspection LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		let enc3 = (chr2 & 0x0F) << 2 | chr3 >> 6;
+		// noinspection LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		let enc4 = chr3 & 0x3F;
+
+		// noinspection ConstantOnRightSideOfComparisonJS
+		if (flag1 === 1) {
+			// noinspection NestedAssignmentJS, AssignmentResultUsedJS, MagicNumberJS
+			enc3 = enc4 = 64;
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS
+			if (flag2 === 1) {
+				// noinspection MagicNumberJS
+				enc4 = 64;
+			}
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (skipPadding) {
+			// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS, MagicNumberJS
+			if (enc3 === 64) output += `${template.charAt(enc1)}${template.charAt(enc2)}`;else {
+				// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS, MagicNumberJS
+				if (enc4 === 64) output += `${template.charAt(enc1)}${template.charAt(enc2)}${template.charAt(enc3)}`;else output += `${template.charAt(enc1)}${template.charAt(enc2)}${template.charAt(enc3)}${template.charAt(enc4)}`;
+			}
+		} else output += `${template.charAt(enc1)}${template.charAt(enc2)}${template.charAt(enc3)}${template.charAt(enc4)}`;
+	}
+
+	return output;
+}
+//**************************************************************************************
+// noinspection FunctionWithMoreThanThreeNegationsJS, FunctionWithMultipleLoopsJS, OverlyComplexFunctionJS, FunctionNamingConventionJS
+/**
+ * Decode string from BASE64 (or "base64url")
+ * @param {string} input
+ * @param {boolean} [useUrlTemplate=false] If "true" then output would be encoded using "base64url"
+ * @param {boolean} [cutTailZeros=false] If "true" then cut tailing zeroz from function result
+ * @returns {string}
+ */
+function fromBase64(input, useUrlTemplate = false, cutTailZeros = false) {
+	// noinspection ConditionalExpressionJS
+	const template = useUrlTemplate ? base64UrlTemplate : base64Template;
+
+	//region Aux functions
+	// noinspection FunctionWithMultipleReturnPointsJS, NestedFunctionJS
+	function indexof(toSearch) {
+		// noinspection ConstantOnRightSideOfComparisonJS, MagicNumberJS
+		for (let i = 0; i < 64; i++) {
+			// noinspection NonBlockStatementBodyJS
+			if (template.charAt(i) === toSearch) return i;
+		}
+
+		// noinspection MagicNumberJS
+		return 64;
+	}
+
+	// noinspection NestedFunctionJS
+	function test(incoming) {
+		// noinspection ConstantOnRightSideOfComparisonJS, ConditionalExpressionJS, MagicNumberJS
+		return incoming === 64 ? 0x00 : incoming;
+	}
+	//endregion
+
+	let i = 0;
+
+	let output = "";
+
+	while (i < input.length) {
+		// noinspection NestedFunctionCallJS, LocalVariableNamingConventionJS, IncrementDecrementResultUsedJS
+		const enc1 = indexof(input.charAt(i++));
+		// noinspection NestedFunctionCallJS, LocalVariableNamingConventionJS, ConditionalExpressionJS, MagicNumberJS, IncrementDecrementResultUsedJS
+		const enc2 = i >= input.length ? 0x00 : indexof(input.charAt(i++));
+		// noinspection NestedFunctionCallJS, LocalVariableNamingConventionJS, ConditionalExpressionJS, MagicNumberJS, IncrementDecrementResultUsedJS
+		const enc3 = i >= input.length ? 0x00 : indexof(input.charAt(i++));
+		// noinspection NestedFunctionCallJS, LocalVariableNamingConventionJS, ConditionalExpressionJS, MagicNumberJS, IncrementDecrementResultUsedJS
+		const enc4 = i >= input.length ? 0x00 : indexof(input.charAt(i++));
+
+		// noinspection LocalVariableNamingConventionJS, NonShortCircuitBooleanExpressionJS
+		const chr1 = test(enc1) << 2 | test(enc2) >> 4;
+		// noinspection LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		const chr2 = (test(enc2) & 0x0F) << 4 | test(enc3) >> 2;
+		// noinspection LocalVariableNamingConventionJS, MagicNumberJS, NonShortCircuitBooleanExpressionJS
+		const chr3 = (test(enc3) & 0x03) << 6 | test(enc4);
+
+		output += String.fromCharCode(chr1);
+
+		// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS, MagicNumberJS
+		if (enc3 !== 64) output += String.fromCharCode(chr2);
+
+		// noinspection ConstantOnRightSideOfComparisonJS, NonBlockStatementBodyJS, MagicNumberJS
+		if (enc4 !== 64) output += String.fromCharCode(chr3);
+	}
+
+	if (cutTailZeros) {
+		const outputLength = output.length;
+		let nonZeroStart = -1;
+
+		// noinspection ConstantOnRightSideOfComparisonJS
+		for (let i = outputLength - 1; i >= 0; i--) {
+			// noinspection ConstantOnRightSideOfComparisonJS
+			if (output.charCodeAt(i) !== 0) {
+				nonZeroStart = i;
+				// noinspection BreakStatementJS
+				break;
+			}
+		}
+
+		// noinspection NonBlockStatementBodyJS, NegatedIfStatementJS
+		if (nonZeroStart !== -1) output = output.slice(0, nonZeroStart + 1);else output = "";
+	}
+
+	return output;
+}
+//**************************************************************************************
+function arrayBufferToString(buffer) {
+	let resultString = "";
+	const view = new Uint8Array(buffer);
+
+	// noinspection NonBlockStatementBodyJS
+	var _iteratorNormalCompletion6 = true;
+	var _didIteratorError6 = false;
+	var _iteratorError6 = undefined;
+
+	try {
+		for (var _iterator6 = view[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
+			const element = _step6.value;
+
+			resultString += String.fromCharCode(element);
+		}
+	} catch (err) {
+		_didIteratorError6 = true;
+		_iteratorError6 = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion6 && _iterator6.return) {
+				_iterator6.return();
+			}
+		} finally {
+			if (_didIteratorError6) {
+				throw _iteratorError6;
+			}
+		}
+	}
+
+	return resultString;
+}
+//**************************************************************************************
+function stringToArrayBuffer(str) {
+	const stringLength = str.length;
+
+	const resultBuffer = new ArrayBuffer(stringLength);
+	const resultView = new Uint8Array(resultBuffer);
+
+	// noinspection NonBlockStatementBodyJS
+	for (let i = 0; i < stringLength; i++) resultView[i] = str.charCodeAt(i);
+
+	return resultBuffer;
+}
+//**************************************************************************************
+const log2 = Math.log(2);
+//**************************************************************************************
+// noinspection FunctionNamingConventionJS
+/**
+ * Get nearest to input length power of 2
+ * @param {number} length Current length of existing array
+ * @returns {number}
+ */
+function nearestPowerOf2(length) {
+	const base = Math.log(length) / log2;
+
+	const floor = Math.floor(base);
+	const round = Math.round(base);
+
+	// noinspection ConditionalExpressionJS
+	return floor === round ? floor : round;
+}
+//**************************************************************************************
+/**
+ * Delete properties by name from specified object
+ * @param {Object} object Object to delete properties from
+ * @param {Array.<string>} propsArray Array of properties names
+ */
+function clearProps(object, propsArray) {
+	var _iteratorNormalCompletion7 = true;
+	var _didIteratorError7 = false;
+	var _iteratorError7 = undefined;
+
+	try {
+		for (var _iterator7 = propsArray[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
+			const prop = _step7.value;
+
+			delete object[prop];
+		}
+	} catch (err) {
+		_didIteratorError7 = true;
+		_iteratorError7 = err;
+	} finally {
+		try {
+			if (!_iteratorNormalCompletion7 && _iterator7.return) {
+				_iterator7.return();
+			}
+		} finally {
+			if (_didIteratorError7) {
+				throw _iteratorError7;
+			}
+		}
+	}
+}
+//**************************************************************************************
+
+},{}]},{},[1])(1)
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/vendor/pkijs_bundle.js
@@ -0,0 +1,46647 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pkijs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+	value: true
+});
+exports.parseByteMap = parseByteMap;
+/*
+ * Copyright (c) 2016-2018, Peculiar Ventures
+ * All rights reserved.
+ *
+ * Author 2016-2018, Yury Strozhevsky <www.strozhevsky.com>.
+ *
+ */
+//**************************************************************************************
+class ByteStream {
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleLoopsJS
+	/**
+  * Constructor for ByteStream class
+  * @param {{[length]: number, [stub]: number, [view]: Uint8Array, [buffer]: ArrayBuffer, [string]: string, [hexstring]: string}} parameters
+  */
+	constructor(parameters = {}) {
+		this.clear();
+
+		var _iteratorNormalCompletion = true;
+		var _didIteratorError = false;
+		var _iteratorError = undefined;
+
+		try {
+			for (var _iterator = Object.keys(parameters)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+				const key = _step.value;
+
+				switch (key) {
+					case "length":
+						this.length = parameters.length;
+						break;
+					case "stub":
+						// noinspection NonBlockStatementBodyJS
+						for (let i = 0; i < this._view.length; i++) this._view[i] = parameters.stub;
+						break;
+					case "view":
+						this.fromUint8Array(parameters.view);
+						break;
+					case "buffer":
+						this.fromArrayBuffer(parameters.buffer);
+						break;
+					case "string":
+						this.fromString(parameters.string);
+						break;
+					case "hexstring":
+						this.fromHexString(parameters.hexstring);
+						break;
+					default:
+				}
+			}
+		} catch (err) {
+			_didIteratorError = true;
+			_iteratorError = err;
+		} finally {
+			try {
+				if (!_iteratorNormalCompletion && _iterator.return) {
+					_iterator.return();
+				}
+			} finally {
+				if (_didIteratorError) {
+					throw _iteratorError;
+				}
+			}
+		}
+	}
+	//**********************************************************************************
+	/**
+  * Setter for "buffer"
+  * @param {ArrayBuffer} value
+  */
+	set buffer(value) {
+		this._buffer = value.slice(0);
+		this._view = new Uint8Array(this._buffer);
+	}
+	//**********************************************************************************
+	/**
+  * Getter for "buffer"
+  * @returns {ArrayBuffer}
+  */
+	get buffer() {
+		return this._buffer;
+	}
+	//**********************************************************************************
+	/**
+  * Setter for "view"
+  * @param {Uint8Array} value
+  */
+	set view(value) {
+		this._buffer = new ArrayBuffer(value.length);
+		this._view = new Uint8Array(this._buffer);
+
+		this._view.set(value);
+	}
+	//**********************************************************************************
+	/**
+  * Getter for "view"
+  * @returns {Uint8Array}
+  */
+	get view() {
+		return this._view;
+	}
+	//**********************************************************************************
+	/**
+  * Getter for "length"
+  * @returns {number}
+  */
+	get length() {
+		return this._buffer.byteLength;
+	}
+	//**********************************************************************************
+	/**
+  * Setter for "length"
+  * @param {number} value
+  */
+	set length(value) {
+		this._buffer = new ArrayBuffer(value);
+		this._view = new Uint8Array(this._buffer);
+	}
+	//**********************************************************************************
+	/**
+  * Clear existing stream
+  */
+	clear() {
+		this._buffer = new ArrayBuffer(0);
+		this._view = new Uint8Array(this._buffer);
+	}
+	//**********************************************************************************
+	/**
+  * Initialize "Stream" object from existing "ArrayBuffer"
+  * @param {!ArrayBuffer} array The ArrayBuffer to copy from
+  */
+	fromArrayBuffer(array) {
+		this.buffer = array;
+	}
+	//**********************************************************************************
+	// noinspection FunctionNamingConventionJS
+	/**
+  * Initialize "Stream" object from existing "Uint8Array"
+  * @param {!Uint8Array} array The Uint8Array to copy from
+  */
+	fromUint8Array(array) {
+		this._buffer = new ArrayBuffer(array.length);
+		this._view = new Uint8Array(this._buffer);
+
+		this._view.set(array);
+	}
+	//**********************************************************************************
+	/**
+  * Initialize "Stream" object from existing string
+  * @param {string} string The string to initialize from
+  */
+	fromString(string) {
+		const stringLength = string.length;
+
+		this.length = stringLength;
+
+		// noinspection NonBlockStatementBodyJS
+		for (let i = 0; i < stringLength; i++) this.view[i] = string.charCodeAt(i);
+	}
+	//**********************************************************************************
+	/**
+  * Represent "Stream" object content as a string
+  * @param {number} [start] Start position to convert to string
+  * @param {number} [length] Length of array to convert to string
+  * @returns {string}
+  */
+	toString(start = 0, length = this.view.length - start) {
+		//region Initial variables
+		let result = "";
+		//endregion
+
+		//region Check input parameters
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start >= this.view.length || start < 0) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length >= this.view.length || length < 0) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.view.length - start;
+		}
+		//endregion
+
+		//region Convert array of bytes to string
+		// noinspection NonBlockStatementBodyJS
+		for (let i = start; i < start + length; i++) result += String.fromCharCode(this.view[i]);
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionTooLongJS
+	/**
+  * Initialize "Stream" object from existing hexdecimal string
+  * @param {string} hexString String to initialize from
+  */
+	fromHexString(hexString) {
+		//region Initial variables
+		const stringLength = hexString.length;
+
+		this.buffer = new ArrayBuffer(stringLength >> 1);
+		this.view = new Uint8Array(this.buffer);
+
+		const hexMap = new Map();
+
+		// noinspection MagicNumberJS
+		hexMap.set("0", 0x00);
+		// noinspection MagicNumberJS
+		hexMap.set("1", 0x01);
+		// noinspection MagicNumberJS
+		hexMap.set("2", 0x02);
+		// noinspection MagicNumberJS
+		hexMap.set("3", 0x03);
+		// noinspection MagicNumberJS
+		hexMap.set("4", 0x04);
+		// noinspection MagicNumberJS
+		hexMap.set("5", 0x05);
+		// noinspection MagicNumberJS
+		hexMap.set("6", 0x06);
+		// noinspection MagicNumberJS
+		hexMap.set("7", 0x07);
+		// noinspection MagicNumberJS
+		hexMap.set("8", 0x08);
+		// noinspection MagicNumberJS
+		hexMap.set("9", 0x09);
+		// noinspection MagicNumberJS
+		hexMap.set("A", 0x0A);
+		// noinspection MagicNumberJS
+		hexMap.set("a", 0x0A);
+		// noinspection MagicNumberJS
+		hexMap.set("B", 0x0B);
+		// noinspection MagicNumberJS
+		hexMap.set("b", 0x0B);
+		// noinspection MagicNumberJS
+		hexMap.set("C", 0x0C);
+		// noinspection MagicNumberJS
+		hexMap.set("c", 0x0C);
+		// noinspection MagicNumberJS
+		hexMap.set("D", 0x0D);
+		// noinspection MagicNumberJS
+		hexMap.set("d", 0x0D);
+		// noinspection MagicNumberJS
+		hexMap.set("E", 0x0E);
+		// noinspection MagicNumberJS
+		hexMap.set("e", 0x0E);
+		// noinspection MagicNumberJS
+		hexMap.set("F", 0x0F);
+		// noinspection MagicNumberJS
+		hexMap.set("f", 0x0F);
+
+		let j = 0;
+		// noinspection MagicNumberJS
+		let temp = 0x00;
+		//endregion
+
+		//region Convert char-by-char
+		for (let i = 0; i < stringLength; i++) {
+			// noinspection NegatedIfStatementJS
+			if (!(i % 2)) {
+				// noinspection NestedFunctionCallJS
+				temp = hexMap.get(hexString.charAt(i)) << 4;
+			} else {
+				// noinspection NestedFunctionCallJS
+				temp |= hexMap.get(hexString.charAt(i));
+
+				this.view[j] = temp;
+				j++;
+			}
+		}
+		//endregion
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols
+	/**
+  * Represent "Stream" object content as a hexdecimal string
+  * @param {number} [start=0] Start position to convert to string
+  * @param {number} [length=(this.view.length - start)] Length of array to convert to string
+  * @returns {string}
+  */
+	toHexString(start = 0, length = this.view.length - start) {
+		//region Initial variables
+		let result = "";
+		//endregion
+
+		//region Check input parameters
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start >= this.view.length || start < 0) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length >= this.view.length || length < 0) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.view.length - start;
+		}
+		//endregion
+
+		for (let i = start; i < start + length; i++) {
+			// noinspection ChainedFunctionCallJS
+			const str = this.view[i].toString(16).toUpperCase();
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, ConditionalExpressionJS, EqualityComparisonWithCoercionJS
+			result = result + (str.length == 1 ? "0" : "") + str;
+		}
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Return copy of existing "Stream"
+  * @param {number} [start=0] Start position of the copy
+  * @param {number} [length=this.view.length] Length of the copy
+  * @returns {ByteStream}
+  */
+	copy(start = 0, length = this._buffer.byteLength - start) {
+		//region Check input parameters
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS
+		if (start === 0 && this._buffer.byteLength === 0) return new ByteStream();
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS
+		if (start < 0 || start > this._buffer.byteLength - 1) throw new Error(`Wrong start position: ${start}`);
+		//endregion
+
+		const stream = new ByteStream();
+
+		stream._buffer = this._buffer.slice(start, start + length);
+		stream._view = new Uint8Array(stream._buffer);
+
+		return stream;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Return slice of existing "Stream"
+  * @param {number} [start=0] Start position of the slice
+  * @param {number} [end=this._buffer.byteLength] End position of the slice
+  * @returns {ByteStream}
+  */
+	slice(start = 0, end = this._buffer.byteLength) {
+		//region Check input parameters
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS
+		if (start === 0 && this._buffer.byteLength === 0) return new ByteStream();
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS
+		if (start < 0 || start > this._buffer.byteLength - 1) throw new Error(`Wrong start position: ${start}`);
+		//endregion
+
+		const stream = new ByteStream();
+
+		stream._buffer = this._buffer.slice(start, end);
+		stream._view = new Uint8Array(stream._buffer);
+
+		return stream;
+	}
+	//**********************************************************************************
+	/**
+  * Change size of existing "Stream"
+  * @param {!number} size Size for new "Stream"
+  */
+	realloc(size) {
+		//region Initial variables
+		const buffer = new ArrayBuffer(size);
+		const view = new Uint8Array(buffer);
+		//endregion
+
+		//region Create a new ArrayBuffer content
+		// noinspection NonBlockStatementBodyJS
+		if (size > this._view.length) view.set(this._view);else {
+			// noinspection NestedFunctionCallJS
+			view.set(new Uint8Array(this._buffer, 0, size));
+		}
+		//endregion
+
+		//region Initialize "Stream" with new "ArrayBuffer"
+		this._buffer = buffer.slice(0);
+		this._view = new Uint8Array(this._buffer);
+		//endregion
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols
+	/**
+  * Append a new "Stream" content to the current "Stream"
+  * @param {ByteStream} stream A new "stream" to append to current "stream"
+  */
+	append(stream) {
+		//region Initial variables
+		const initialSize = this._buffer.byteLength;
+		const streamViewLength = stream._buffer.byteLength;
+
+		const copyView = stream._view.slice();
+		//endregion
+
+		//region Re-allocate current internal buffer
+		this.realloc(initialSize + streamViewLength);
+		//endregion
+
+		//region Copy input stream content to a new place
+		this._view.set(copyView, initialSize);
+		//endregion
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Insert "Stream" content to the current "Stream" at specific position
+  * @param {ByteStream} stream A new "stream" to insert to current "stream"
+  * @param {number} [start=0] Start position to insert to
+  * @param {number} [length]
+  * @returns {boolean}
+  */
+	insert(stream, start = 0, length = this._buffer.byteLength - start) {
+		//region Initial variables
+		// noinspection NonBlockStatementBodyJS
+		if (start > this._buffer.byteLength - 1) return false;
+
+		if (length > this._buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this._buffer.byteLength - start;
+		}
+		//endregion
+
+		//region Check input variables
+		if (length > stream._buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = stream._buffer.byteLength;
+		}
+		//endregion
+
+		//region Update content of the current stream
+		// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (length == stream._buffer.byteLength) this._view.set(stream._view, start);else {
+			// noinspection NestedFunctionCallJS
+			this._view.set(stream._view.slice(0, length), start);
+		}
+		//endregion
+
+		return true;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Check that two "Stream" objects has equal content
+  * @param {ByteStream} stream Stream to compare with
+  * @returns {boolean}
+  */
+	isEqual(stream) {
+		//region Check length of both buffers
+		// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (this._buffer.byteLength != stream._buffer.byteLength) return false;
+		//endregion
+
+		//region Compare each byte of both buffers
+		for (let i = 0; i < stream._buffer.byteLength; i++) {
+			// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+			if (this.view[i] != stream.view[i]) return false;
+		}
+		//endregion
+
+		return true;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Check that current "Stream" objects has equal content with input "Uint8Array"
+  * @param {Uint8Array} view View to compare with
+  * @returns {boolean}
+  */
+	isEqualView(view) {
+		//region Check length of both buffers
+		// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (view.length != this.view.length) return false;
+		//endregion
+
+		//region Compare each byte of both buffers
+		for (let i = 0; i < view.length; i++) {
+			// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+			if (this.view[i] != view[i]) return false;
+		}
+		//endregion
+
+		return true;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS, OverlyComplexFunctionJS, FunctionTooLongJS
+	/**
+  * Find any byte pattern in "Stream"
+  * @param {ByteStream} pattern Stream having pattern value
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @param {boolean} [backward] Flag to search in backward order
+  * @returns {number}
+  */
+	findPattern(pattern, start = null, length = null, backward = false) {
+		//region Check input variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS, ConditionalExpressionJS
+			start = backward ? this.buffer.byteLength : 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		if (backward) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+
+			if (length > start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+
+			if (length > this.buffer.byteLength - start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+		}
+		//endregion
+
+		//region Initial variables
+		const patternLength = pattern.buffer.byteLength;
+		// noinspection NonBlockStatementBodyJS
+		if (patternLength > length) return -1;
+		//endregion
+
+		//region Make a "pre-read" array for pattern
+		const patternArray = [];
+		// noinspection NonBlockStatementBodyJS
+		for (let i = 0; i < patternLength; i++) patternArray.push(pattern.view[i]);
+		//endregion
+
+		//region Search for pattern
+		for (let i = 0; i <= length - patternLength; i++) {
+			let equal = true;
+			// noinspection ConditionalExpressionJS
+			const equalStart = backward ? start - patternLength - i : start + i;
+
+			for (let j = 0; j < patternLength; j++) {
+				// noinspection EqualityComparisonWithCoercionJS
+				if (this.view[j + equalStart] != patternArray[j]) {
+					equal = false;
+					// noinspection BreakStatementJS
+					break;
+				}
+			}
+
+			if (equal) {
+				// noinspection ConditionalExpressionJS
+				return backward ? start - patternLength - i : start + patternLength + i; // Position after the pattern found
+			}
+		}
+		//endregion
+
+		return -1;
+	}
+	//**********************************************************************************
+	// noinspection OverlyComplexFunctionJS
+	/**
+  * Find first position of any pattern from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be found
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @param {boolean} [backward=false] Flag to search in backward order
+  * @returns {{id: number, position: number}}
+  */
+	findFirstIn(patterns, start = null, length = null, backward = false) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS, ConditionalExpressionJS
+			start = backward ? this.buffer.byteLength : 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		if (backward) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+
+			if (length > start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+
+			if (length > this.buffer.byteLength - start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+		}
+
+		// noinspection ConditionalExpressionJS
+		const result = {
+			id: -1,
+			position: backward ? 0 : start + length,
+			length: 0
+		};
+		//endregion
+
+		for (let i = 0; i < patterns.length; i++) {
+			const position = this.findPattern(patterns[i], start, length, backward);
+			// noinspection EqualityComparisonWithCoercionJS
+			if (position != -1) {
+				let valid = false;
+				const patternLength = patterns[i].length;
+
+				if (backward) {
+					// noinspection NonBlockStatementBodyJS
+					if (position - patternLength >= result.position - result.length) valid = true;
+				} else {
+					// noinspection NonBlockStatementBodyJS
+					if (position - patternLength <= result.position - result.length) valid = true;
+				}
+
+				if (valid) {
+					result.position = position;
+					result.id = i;
+					result.length = patternLength;
+				}
+			}
+		}
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Find all positions of any pattern from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be found
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @returns {Array}
+  */
+	findAllIn(patterns, start = 0, length = this.buffer.byteLength - start) {
+		//region Initial variables
+		const result = [];
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (start > this.buffer.byteLength - 1) return result;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		let patternFound = {
+			id: -1,
+			position: start
+		};
+		//endregion
+
+		//region Find all accurences of patterns
+		do {
+			const position = patternFound.position;
+
+			patternFound = this.findFirstIn(patterns, patternFound.position, length);
+
+			// noinspection EqualityComparisonWithCoercionJS
+			if (patternFound.id == -1) {
+				// noinspection BreakStatementJS
+				break;
+			}
+
+			// noinspection AssignmentToFunctionParameterJS
+			length -= patternFound.position - position;
+
+			result.push({
+				id: patternFound.id,
+				position: patternFound.position
+			});
+		} while (true); // eslint-disable-line
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS
+	/**
+  * Find all positions of a pattern
+  * @param {ByteStream} pattern Stream having pattern value
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @returns {Array|number} Array with all pattern positions or (-1) if failed
+  */
+	findAllPatternIn(pattern, start = 0, length = this.buffer.byteLength - start) {
+		//region Check input variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+		//endregion
+
+		//region Initial variables
+		const result = [];
+
+		const patternLength = pattern.buffer.byteLength;
+		// noinspection NonBlockStatementBodyJS
+		if (patternLength > length) return -1;
+		//endregion
+
+		//region Make a "pre-read" array for pattern
+		const patternArray = Array.from(pattern.view);
+		//endregion
+
+		//region Search for pattern
+		for (let i = 0; i <= length - patternLength; i++) {
+			let equal = true;
+			const equalStart = start + i;
+
+			for (let j = 0; j < patternLength; j++) {
+				// noinspection EqualityComparisonWithCoercionJS
+				if (this.view[j + equalStart] != patternArray[j]) {
+					equal = false;
+					// noinspection BreakStatementJS
+					break;
+				}
+			}
+
+			if (equal) {
+				result.push(start + patternLength + i); // Position after the pattern found
+				i += patternLength - 1; // On next step of "for" we will have "i++"
+			}
+		}
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection OverlyComplexFunctionJS, FunctionTooLongJS
+	/**
+  * Find first position of data, not included in patterns from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be ommited
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @param {boolean} [backward=false] Flag to search in backward order
+  * @returns {{left: {id: number, position: *}, right: {id: number, position: number}, value: ByteStream}}
+  */
+	findFirstNotIn(patterns, start = null, length = null, backward = false) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS, ConditionalExpressionJS
+			start = backward ? this.buffer.byteLength : 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		if (backward) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+
+			if (length > start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+
+			if (length > this.buffer.byteLength - start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+		}
+
+		const result = {
+			left: {
+				id: -1,
+				position: start
+			},
+			right: {
+				id: -1,
+				position: 0
+			},
+			value: new ByteStream()
+		};
+
+		let currentLength = length;
+		//endregion
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		while (currentLength > 0) {
+			//region Search for nearest "pattern"
+			// noinspection ConditionalExpressionJS
+			result.right = this.findFirstIn(patterns, backward ? start - length + currentLength : start + length - currentLength, currentLength, backward);
+			//endregion
+
+			//region No pattern at all
+			// noinspection EqualityComparisonWithCoercionJS
+			if (result.right.id == -1) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = currentLength;
+
+				if (backward) {
+					// noinspection AssignmentToFunctionParameterJS
+					start -= length;
+				} else {
+					// noinspection AssignmentToFunctionParameterJS
+					start = result.left.position;
+				}
+
+				result.value = new ByteStream();
+
+				result.value._buffer = this._buffer.slice(start, start + length);
+				result.value._view = new Uint8Array(result.value._buffer);
+
+				// noinspection BreakStatementJS
+				break;
+			}
+			//endregion
+
+			//region Check distance between two patterns
+			// noinspection ConditionalExpressionJS, EqualityComparisonWithCoercionJS
+			if (result.right.position != (backward ? result.left.position - patterns[result.right.id].buffer.byteLength : result.left.position + patterns[result.right.id].buffer.byteLength)) {
+				if (backward) {
+					// noinspection AssignmentToFunctionParameterJS
+					start = result.right.position + patterns[result.right.id].buffer.byteLength;
+					// noinspection AssignmentToFunctionParameterJS
+					length = result.left.position - result.right.position - patterns[result.right.id].buffer.byteLength;
+				} else {
+					// noinspection AssignmentToFunctionParameterJS
+					start = result.left.position;
+					// noinspection AssignmentToFunctionParameterJS
+					length = result.right.position - result.left.position - patterns[result.right.id].buffer.byteLength;
+				}
+
+				result.value = new ByteStream();
+
+				result.value._buffer = this._buffer.slice(start, start + length);
+				result.value._view = new Uint8Array(result.value._buffer);
+
+				// noinspection BreakStatementJS
+				break;
+			}
+			//endregion
+
+			//region Store information about previous pattern
+			result.left = result.right;
+			//endregion
+
+			//region Change current length
+			currentLength -= patterns[result.right.id]._buffer.byteLength;
+			//endregion
+		}
+
+		//region Swap "patterns" in case of backward order
+		if (backward) {
+			const temp = result.right;
+			result.right = result.left;
+			result.left = temp;
+		}
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Find all positions of data, not included in patterns from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be ommited
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @returns {Array}
+  */
+	findAllNotIn(patterns, start = null, length = null) {
+		//region Initial variables
+		const result = [];
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (start > this.buffer.byteLength - 1) return result;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		let patternFound = {
+			left: {
+				id: -1,
+				position: start
+			},
+			right: {
+				id: -1,
+				position: start
+			},
+			value: new ByteStream()
+		};
+		//endregion
+
+		//region Find all accurences of patterns
+		// noinspection EqualityComparisonWithCoercionJS
+		do {
+			const position = patternFound.right.position;
+
+			patternFound = this.findFirstNotIn(patterns, patternFound.right.position, length);
+
+			// noinspection AssignmentToFunctionParameterJS
+			length -= patternFound.right.position - position;
+
+			result.push({
+				left: {
+					id: patternFound.left.id,
+					position: patternFound.left.position
+				},
+				right: {
+					id: patternFound.right.id,
+					position: patternFound.right.position
+				},
+				value: patternFound.value
+			});
+		} while (patternFound.right.id != -1);
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS, OverlyComplexFunctionJS
+	/**
+  * Find position of a sequence of any patterns from input array
+  * @param {Array.<ByteStream>} patterns Array of pattern to look for
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @param {boolean} [backward=false] Flag to search in backward order
+  * @returns {*}
+  */
+	findFirstSequence(patterns, start = null, length = null, backward = false) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS, ConditionalExpressionJS
+			start = backward ? this.buffer.byteLength : 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		if (backward) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+
+			if (length > start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+
+			if (length > this.buffer.byteLength - start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+		}
+		//endregion
+
+		//region Find first byte from sequence
+		const firstIn = this.skipNotPatterns(patterns, start, length, backward);
+		// noinspection EqualityComparisonWithCoercionJS
+		if (firstIn == -1) {
+			return {
+				position: -1,
+				value: new ByteStream()
+			};
+		}
+		//endregion
+
+		//region Find first byte not in sequence
+		// noinspection ConditionalExpressionJS
+		const firstNotIn = this.skipPatterns(patterns, firstIn, length - (backward ? start - firstIn : firstIn - start), backward);
+		//endregion
+
+		//region Make output value
+		if (backward) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = firstNotIn;
+			// noinspection AssignmentToFunctionParameterJS
+			length = firstIn - firstNotIn;
+		} else {
+			// noinspection AssignmentToFunctionParameterJS
+			start = firstIn;
+			// noinspection AssignmentToFunctionParameterJS
+			length = firstNotIn - firstIn;
+		}
+
+		const value = new ByteStream();
+
+		value._buffer = this._buffer.slice(start, start + length);
+		value._view = new Uint8Array(value._buffer);
+		//endregion
+
+		return {
+			position: firstNotIn,
+			value
+		};
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Find all positions of a sequence of any patterns from input array
+  * @param {Array.<ByteStream>} patterns Array of patterns to search for
+  * @param {?number} [start] Start position to search from
+  * @param {?number} [length] Length of byte block to search at
+  * @returns {Array}
+  */
+	findAllSequences(patterns, start = null, length = null) {
+		//region Initial variables
+		const result = [];
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (start > this.buffer.byteLength - 1) return result;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		let patternFound = {
+			position: start,
+			value: new ByteStream()
+		};
+		//endregion
+
+		//region Find all accurences of patterns
+		// noinspection EqualityComparisonWithCoercionJS
+		do {
+			const position = patternFound.position;
+
+			patternFound = this.findFirstSequence(patterns, patternFound.position, length);
+
+			// noinspection EqualityComparisonWithCoercionJS
+			if (patternFound.position != -1) {
+				// noinspection AssignmentToFunctionParameterJS
+				length -= patternFound.position - position;
+
+				result.push({
+					position: patternFound.position,
+					value: patternFound.value
+				});
+			}
+		} while (patternFound.position != -1);
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS, OverlyComplexFunctionJS, FunctionTooLongJS
+	/**
+  * Find all paired patterns in the stream
+  * @param {ByteStream} leftPattern Left pattern to search for
+  * @param {ByteStream} rightPattern Right pattern to search for
+  * @param {?number} [start=null] Start position to search from
+  * @param {?number} [length=null] Length of byte block to search at
+  * @returns {Array}
+  */
+	findPairedPatterns(leftPattern, rightPattern, start = null, length = null) {
+		//region Initial variables
+		const result = [];
+
+		// noinspection NonBlockStatementBodyJS
+		if (leftPattern.isEqual(rightPattern)) return result;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (start > this.buffer.byteLength - 1) return result;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		let currentPositionLeft = 0;
+		//endregion
+
+		//region Find all "left patterns" as sorted array
+		const leftPatterns = this.findAllPatternIn(leftPattern, start, length);
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (leftPatterns.length == 0) return result;
+		//endregion
+
+		//region Find all "right patterns" as sorted array
+		const rightPatterns = this.findAllPatternIn(rightPattern, start, length);
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (rightPatterns.length == 0) return result;
+		//endregion
+
+		//region Combine patterns
+		while (currentPositionLeft < leftPatterns.length) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, EqualityComparisonWithCoercionJS
+			if (rightPatterns.length == 0) {
+				// noinspection BreakStatementJS
+				break;
+			}
+
+			// noinspection EqualityComparisonWithCoercionJS
+			if (leftPatterns[0] == rightPatterns[0]) {
+				// Possible situation when one pattern is a part of another
+				// For example "stream" and "endstream"
+				// In case when we have only "endstream" in fact "stream" will be also found at the same position
+				// (position of the pattern is an index AFTER the pattern)
+
+				result.push({
+					left: leftPatterns[0],
+					right: rightPatterns[0]
+				});
+
+				leftPatterns.splice(0, 1);
+				rightPatterns.splice(0, 1);
+
+				// noinspection ContinueStatementJS
+				continue;
+			}
+
+			if (leftPatterns[currentPositionLeft] > rightPatterns[0]) {
+				// noinspection BreakStatementJS
+				break;
+			}
+
+			while (leftPatterns[currentPositionLeft] < rightPatterns[0]) {
+				currentPositionLeft++;
+
+				if (currentPositionLeft >= leftPatterns.length) {
+					// noinspection BreakStatementJS
+					break;
+				}
+			}
+
+			result.push({
+				left: leftPatterns[currentPositionLeft - 1],
+				right: rightPatterns[0]
+			});
+
+			leftPatterns.splice(currentPositionLeft - 1, 1);
+			rightPatterns.splice(0, 1);
+
+			currentPositionLeft = 0;
+		}
+		//endregion
+
+		//region Sort result
+		result.sort((a, b) => a.left - b.left);
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS, OverlyComplexFunctionJS, FunctionTooLongJS
+	/**
+  * Find all paired patterns in the stream
+  * @param {Array.<ByteStream>} inputLeftPatterns Array of left patterns to search for
+  * @param {Array.<ByteStream>} inputRightPatterns Array of right patterns to search for
+  * @param {?number} [start=null] Start position to search from
+  * @param {?number} [length=null] Length of byte block to search at
+  * @returns {Array}
+  */
+	findPairedArrays(inputLeftPatterns, inputRightPatterns, start = null, length = null) {
+		//region Initial variables
+		const result = [];
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (start > this.buffer.byteLength - 1) return result;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		let currentPositionLeft = 0;
+		//endregion
+
+		//region Find all "left patterns" as sorted array
+		const leftPatterns = this.findAllIn(inputLeftPatterns, start, length);
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (leftPatterns.length == 0) return result;
+		//endregion
+
+		//region Find all "right patterns" as sorted array
+		const rightPatterns = this.findAllIn(inputRightPatterns, start, length);
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (rightPatterns.length == 0) return result;
+		//endregion
+
+		//region Combine patterns
+		while (currentPositionLeft < leftPatterns.length) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, EqualityComparisonWithCoercionJS
+			if (rightPatterns.length == 0) {
+				// noinspection BreakStatementJS
+				break;
+			}
+
+			// noinspection EqualityComparisonWithCoercionJS
+			if (leftPatterns[0].position == rightPatterns[0].position) {
+				// Possible situation when one pattern is a part of another
+				// For example "stream" and "endstream"
+				// In case when we have only "endstream" in fact "stream" will be also found at the same position
+				// (position of the pattern is an index AFTER the pattern)
+
+				result.push({
+					left: leftPatterns[0],
+					right: rightPatterns[0]
+				});
+
+				leftPatterns.splice(0, 1);
+				rightPatterns.splice(0, 1);
+
+				// noinspection ContinueStatementJS
+				continue;
+			}
+
+			if (leftPatterns[currentPositionLeft].position > rightPatterns[0].position) {
+				// noinspection BreakStatementJS
+				break;
+			}
+
+			while (leftPatterns[currentPositionLeft].position < rightPatterns[0].position) {
+				currentPositionLeft++;
+
+				if (currentPositionLeft >= leftPatterns.length) {
+					// noinspection BreakStatementJS
+					break;
+				}
+			}
+
+			result.push({
+				left: leftPatterns[currentPositionLeft - 1],
+				right: rightPatterns[0]
+			});
+
+			leftPatterns.splice(currentPositionLeft - 1, 1);
+			rightPatterns.splice(0, 1);
+
+			currentPositionLeft = 0;
+		}
+		//endregion
+
+		//region Sort result
+		result.sort((a, b) => a.left.position - b.left.position);
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS, FunctionTooLongJS
+	/**
+  * Replace one patter with other
+  * @param {ByteStream} searchPattern The pattern to search for
+  * @param {ByteStream} replacePattern The pattern to replace initial pattern
+  * @param {?number} [start=null] Start position to search from
+  * @param {?number} [length=null] Length of byte block to search at
+  * @param {Array|null} [findAllResult=null] Pre-calculated results of "findAllIn"
+  * @returns {*}
+  */
+	replacePattern(searchPattern, replacePattern, start = null, length = null, findAllResult = null) {
+		//region Initial variables
+		let result;
+
+		let i;
+		const output = {
+			status: -1,
+			searchPatternPositions: [],
+			replacePatternPositions: []
+		};
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = 0;
+		}
+
+		// noinspection NonBlockStatementBodyJS
+		if (start > this.buffer.byteLength - 1) return false;
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+
+		if (length > this.buffer.byteLength - start) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this.buffer.byteLength - start;
+		}
+		//endregion
+
+		//region Find a pattern to search for
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS
+		if (findAllResult == null) {
+			result = this.findAllIn([searchPattern], start, length);
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+			if (result.length == 0) return output;
+		} else result = findAllResult;
+
+		// noinspection NestedFunctionCallJS
+		output.searchPatternPositions.push(...Array.from(result, element => element.position));
+		//endregion
+
+		//region Variables for new buffer initialization
+		const patternDifference = searchPattern.buffer.byteLength - replacePattern.buffer.byteLength;
+
+		const changedBuffer = new ArrayBuffer(this.view.length - result.length * patternDifference);
+		const changedView = new Uint8Array(changedBuffer);
+		//endregion
+
+		//region Copy data from 0 to start
+		// noinspection NestedFunctionCallJS
+		changedView.set(new Uint8Array(this.buffer, 0, start));
+		//endregion
+
+		//region Replace pattern
+		for (i = 0; i < result.length; i++) {
+			//region Initial variables
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, ConditionalExpressionJS, EqualityComparisonWithCoercionJS
+			const currentPosition = i == 0 ? start : result[i - 1].position;
+			//endregion
+
+			//region Copy bytes other then search pattern
+			// noinspection NestedFunctionCallJS
+			changedView.set(new Uint8Array(this.buffer, currentPosition, result[i].position - searchPattern.buffer.byteLength - currentPosition), currentPosition - i * patternDifference);
+			//endregion
+
+			//region Put replace pattern in a new buffer
+			changedView.set(replacePattern.view, result[i].position - searchPattern.buffer.byteLength - i * patternDifference);
+
+			output.replacePatternPositions.push(result[i].position - searchPattern.buffer.byteLength - i * patternDifference);
+			//endregion
+		}
+		//endregion
+
+		//region Copy data from the end of old buffer
+		i--;
+		// noinspection NestedFunctionCallJS
+		changedView.set(new Uint8Array(this.buffer, result[i].position, this.buffer.byteLength - result[i].position), result[i].position - searchPattern.buffer.byteLength + replacePattern.buffer.byteLength - i * patternDifference);
+		//endregion
+
+		//region Re-initialize existing buffer
+		this.buffer = changedBuffer;
+		this.view = new Uint8Array(this.buffer);
+		//endregion
+
+		output.status = 1;
+
+		return output;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleLoopsJS, FunctionWithMultipleReturnPointsJS, OverlyComplexFunctionJS, FunctionTooLongJS
+	/**
+  * Skip any pattern from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be ommited
+  * @param {?number} [start=null] Start position to search from
+  * @param {?number} [length=null] Length of byte block to search at
+  * @param {boolean} [backward=false] Flag to search in backward order
+  * @returns {*}
+  */
+	skipPatterns(patterns, start = null, length = null, backward = false) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS, ConditionalExpressionJS
+			start = backward ? this.buffer.byteLength : 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		if (backward) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+
+			if (length > start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+
+			if (length > this.buffer.byteLength - start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+		}
+
+		let result = start;
+		//endregion
+
+		//region Search for pattern
+		for (let k = 0; k < patterns.length; k++) {
+			const patternLength = patterns[k].buffer.byteLength;
+			// noinspection ConditionalExpressionJS
+			const equalStart = backward ? result - patternLength : result;
+			let equal = true;
+
+			for (let j = 0; j < patternLength; j++) {
+				// noinspection EqualityComparisonWithCoercionJS
+				if (this.view[j + equalStart] != patterns[k].view[j]) {
+					equal = false;
+					// noinspection BreakStatementJS
+					break;
+				}
+			}
+
+			if (equal) {
+				k = -1;
+
+				if (backward) {
+					result -= patternLength;
+					// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS
+					if (result <= 0) return result;
+				} else {
+					result += patternLength;
+					// noinspection NonBlockStatementBodyJS
+					if (result >= start + length) return result;
+				}
+			}
+		}
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleLoopsJS, OverlyComplexFunctionJS, FunctionTooLongJS
+	/**
+  * Skip any pattern not from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should not be ommited
+  * @param start
+  * @param length
+  * @param backward
+  * @returns {number}
+  */
+	skipNotPatterns(patterns, start = null, length = null, backward = false) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (start == null) {
+			// noinspection AssignmentToFunctionParameterJS, ConditionalExpressionJS
+			start = backward ? this.buffer.byteLength : 0;
+		}
+
+		if (start > this.buffer.byteLength) {
+			// noinspection AssignmentToFunctionParameterJS
+			start = this.buffer.byteLength;
+		}
+
+		if (backward) {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+
+			if (length > start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = start;
+			}
+		} else {
+			// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+			if (length == null) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+
+			if (length > this.buffer.byteLength - start) {
+				// noinspection AssignmentToFunctionParameterJS
+				length = this.buffer.byteLength - start;
+			}
+		}
+
+		let result = -1;
+		//endregion
+
+		//region Search for pattern
+		for (let i = 0; i < length; i++) {
+			for (let k = 0; k < patterns.length; k++) {
+				const patternLength = patterns[k].buffer.byteLength;
+				// noinspection ConditionalExpressionJS
+				const equalStart = backward ? start - i - patternLength : start + i;
+				let equal = true;
+
+				for (let j = 0; j < patternLength; j++) {
+					// noinspection EqualityComparisonWithCoercionJS
+					if (this.view[j + equalStart] != patterns[k].view[j]) {
+						equal = false;
+						// noinspection BreakStatementJS
+						break;
+					}
+				}
+
+				if (equal) {
+					// noinspection ConditionalExpressionJS
+					result = backward ? start - i : start + i; // Exact position of pattern found
+					// noinspection BreakStatementJS
+					break;
+				}
+			}
+
+			// noinspection EqualityComparisonWithCoercionJS
+			if (result != -1) {
+				// noinspection BreakStatementJS
+				break;
+			}
+		}
+		//endregion
+
+		return result;
+	}
+	//**********************************************************************************
+}
+exports.ByteStream = ByteStream; //**************************************************************************************
+
+class SeqStream {
+	//**********************************************************************************
+	/**
+  * Constructor for "SeqStream" class
+  * @param {{[stream]: ByteStream, [length]: number, [backward]: boolean, [start]: number, [appendBlock]: number}} parameters
+  */
+	constructor(parameters = {}) {
+		/**
+   * Major stream
+   * @type {ByteStream}
+   */
+		this.stream = new ByteStream();
+		/**
+   * Length of the major stream
+   * @type {number}
+   */
+		this._length = 0;
+		/**
+   * Flag to search in backward direction
+   * @type {boolean}
+   */
+		this.backward = false;
+		/**
+   * Start position to search
+   * @type {number}
+   */
+		this._start = 0;
+		/**
+   * Length of a block when append information to major stream
+   * @type {number}
+   */
+		this.appendBlock = 0;
+
+		this.prevLength = 0;
+		this.prevStart = 0;
+
+		var _iteratorNormalCompletion2 = true;
+		var _didIteratorError2 = false;
+		var _iteratorError2 = undefined;
+
+		try {
+			for (var _iterator2 = Object.keys(parameters)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+				const key = _step2.value;
+
+				switch (key) {
+					case "stream":
+						this.stream = parameters.stream;
+						break;
+					case "backward":
+						this.backward = parameters.backward;
+						// noinspection JSUnusedGlobalSymbols
+						this._start = this.stream.buffer.byteLength;
+						break;
+					case "length":
+						// noinspection JSUnusedGlobalSymbols
+						this._length = parameters.length;
+						break;
+					case "start":
+						// noinspection JSUnusedGlobalSymbols
+						this._start = parameters.start;
+						break;
+					case "appendBlock":
+						this.appendBlock = parameters.appendBlock;
+						break;
+					case "view":
+						this.stream = new ByteStream({ view: parameters.view });
+						break;
+					case "buffer":
+						this.stream = new ByteStream({ buffer: parameters.buffer });
+						break;
+					case "string":
+						this.stream = new ByteStream({ string: parameters.string });
+						break;
+					case "hexstring":
+						this.stream = new ByteStream({ hexstring: parameters.hexstring });
+						break;
+					default:
+				}
+			}
+		} catch (err) {
+			_didIteratorError2 = true;
+			_iteratorError2 = err;
+		} finally {
+			try {
+				if (!_iteratorNormalCompletion2 && _iterator2.return) {
+					_iterator2.return();
+				}
+			} finally {
+				if (_didIteratorError2) {
+					throw _iteratorError2;
+				}
+			}
+		}
+	}
+	//**********************************************************************************
+	/**
+  * Setter for "stream" property
+  * @param {ByteStream} value
+  */
+	set stream(value) {
+		this._stream = value;
+
+		this.prevLength = this._length;
+		// noinspection JSUnusedGlobalSymbols
+		this._length = value._buffer.byteLength;
+
+		this.prevStart = this._start;
+		// noinspection JSUnusedGlobalSymbols
+		this._start = 0;
+	}
+	//**********************************************************************************
+	/**
+  * Getter for "stream" property
+  * @returns {ByteStream}
+  */
+	get stream() {
+		return this._stream;
+	}
+	//**********************************************************************************
+	/**
+  * Setter for "length" property
+  * @param {number} value
+  */
+	set length(value) {
+		this.prevLength = this._length;
+		// noinspection JSUnusedGlobalSymbols
+		this._length = value;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Getter for "length" property
+  * @returns {number}
+  */
+	get length() {
+		// noinspection NonBlockStatementBodyJS
+		if (this.appendBlock) return this.start;
+
+		return this._length;
+	}
+	//**********************************************************************************
+	// noinspection FunctionWithMultipleReturnPointsJS
+	/**
+  * Setter for "start" property
+  * @param {number} value
+  */
+	set start(value) {
+		// noinspection NonBlockStatementBodyJS
+		if (value > this.stream.buffer.byteLength) return;
+
+		//region Initialization of "prev" internal variables
+		this.prevStart = this._start;
+		this.prevLength = this._length;
+		//endregion
+
+		// noinspection JSUnusedGlobalSymbols, ConditionalExpressionJS
+		this._length -= this.backward ? this._start - value : value - this._start;
+		// noinspection JSUnusedGlobalSymbols
+		this._start = value;
+	}
+	//**********************************************************************************
+	/**
+  * Getter for "start" property
+  * @returns {number}
+  */
+	get start() {
+		return this._start;
+	}
+	//**********************************************************************************
+	/**
+  * Return ArrayBuffer with having value of existing SeqStream length
+  * @return {ArrayBuffer}
+  */
+	get buffer() {
+		return this._stream._buffer.slice(0, this._length);
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols
+	/**
+  * Reset current position of the "SeqStream"
+  */
+	resetPosition() {
+		// noinspection JSUnusedGlobalSymbols
+		this._start = this.prevStart;
+		// noinspection JSUnusedGlobalSymbols
+		this._length = this.prevLength;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Find any byte pattern in "ByteStream"
+  * @param {ByteStream} pattern Stream having pattern value
+  * @param {?number} [gap] Maximum gap between start position and position of nearest object
+  * @returns {number}
+  */
+	findPattern(pattern, gap = null) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (gap == null || gap > this.length) {
+			// noinspection AssignmentToFunctionParameterJS
+			gap = this.length;
+		}
+		//endregion
+
+		//region Find pattern
+		const result = this.stream.findPattern(pattern, this.start, this.length, this.backward);
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (result == -1) return result;
+
+		if (this.backward) {
+			// noinspection NonBlockStatementBodyJS
+			if (result < this.start - pattern.buffer.byteLength - gap) return -1;
+		} else {
+			// noinspection NonBlockStatementBodyJS
+			if (result > this.start + pattern.buffer.byteLength + gap) return -1;
+		}
+		//endregion
+
+		//region Create new values
+		this.start = result;
+		//endregion ;
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Find first position of any pattern from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be found
+  * @param {?number} [gap] Maximum gap between start position and position of nearest object
+  * @returns {{id: number, position: number}}
+  */
+	findFirstIn(patterns, gap = null) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (gap == null || gap > this.length) {
+			// noinspection AssignmentToFunctionParameterJS
+			gap = this.length;
+		}
+		//endregion
+
+		//region Search for patterns
+		const result = this.stream.findFirstIn(patterns, this.start, this.length, this.backward);
+		// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (result.id == -1) return result;
+
+		if (this.backward) {
+			if (result.position < this.start - patterns[result.id].buffer.byteLength - gap) {
+				// noinspection ConditionalExpressionJS
+				return {
+					id: -1,
+					position: this.backward ? 0 : this.start + this.length
+				};
+			}
+		} else {
+			if (result.position > this.start + patterns[result.id].buffer.byteLength + gap) {
+				// noinspection ConditionalExpressionJS
+				return {
+					id: -1,
+					position: this.backward ? 0 : this.start + this.length
+				};
+			}
+		}
+		//endregion
+
+		//region Create new values
+		this.start = result.position;
+		//endregion ;
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols
+	/**
+  * Find all positions of any pattern from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be found
+  * @returns {Array}
+  */
+	findAllIn(patterns) {
+		// In case of "backward order" the start position is at the end on stream.
+		// In case of "normal order" the start position is at the begging of the stream.
+		// But in fact for search for all patterns we need to have start position in "normal order".
+		// noinspection ConditionalExpressionJS
+		const start = this.backward ? this.start - this.length : this.start;
+
+		return this.stream.findAllIn(patterns, start, this.length);
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS, OverlyComplexFunctionJS
+	/**
+  * Find first position of data, not included in patterns from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be ommited
+  * @param {?number} gap Maximum gap between start position and position of nearest object
+  * @returns {*}
+  */
+	findFirstNotIn(patterns, gap = null) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (gap == null || gap > this._length) {
+			// noinspection AssignmentToFunctionParameterJS
+			gap = this._length;
+		}
+		//endregion
+
+		//region Search for patterns
+		const result = this._stream.findFirstNotIn(patterns, this._start, this._length, this.backward);
+		// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (result.left.id == -1 && result.right.id == -1) return result;
+
+		if (this.backward) {
+			// noinspection EqualityComparisonWithCoercionJS
+			if (result.right.id != -1) {
+				if (result.right.position < this._start - patterns[result.right.id]._buffer.byteLength - gap) {
+					return {
+						left: {
+							id: -1,
+							position: this._start
+						},
+						right: {
+							id: -1,
+							position: 0
+						},
+						value: new ByteStream()
+					};
+				}
+			}
+		} else {
+			// noinspection EqualityComparisonWithCoercionJS
+			if (result.left.id != -1) {
+				if (result.left.position > this._start + patterns[result.left.id]._buffer.byteLength + gap) {
+					return {
+						left: {
+							id: -1,
+							position: this._start
+						},
+						right: {
+							id: -1,
+							position: 0
+						},
+						value: new ByteStream()
+					};
+				}
+			}
+		}
+		//endregion
+
+		//region Create new values
+		if (this.backward) {
+			// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+			if (result.left.id == -1) this.start = 0;else this.start = result.left.position;
+		} else {
+			// noinspection NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+			if (result.right.id == -1) this.start = this._start + this._length;else this.start = result.right.position;
+		}
+		//endregion ;
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols
+	/**
+  * Find all positions of data, not included in patterns from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be ommited
+  * @returns {Array}
+  */
+	findAllNotIn(patterns) {
+		// In case of "backward order" the start position is at the end on stream.
+		// In case of "normal order" the start position is at the begging of the stream.
+		// But in fact for search for all patterns we need to have start position in "normal order".
+		// noinspection ConditionalExpressionJS
+		const start = this.backward ? this._start - this._length : this._start;
+
+		return this._stream.findAllNotIn(patterns, start, this._length);
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Find position of a sequence of any patterns from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be ommited
+  * @param {?number} [length] Length to search sequence for
+  * @param {?number} [gap] Maximum gap between start position and position of nearest object
+  * @returns {*}
+  */
+	findFirstSequence(patterns, length = null, gap = null) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (length == null || length > this._length) {
+			// noinspection AssignmentToFunctionParameterJS
+			length = this._length;
+		}
+
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (gap == null || gap > length) {
+			// noinspection AssignmentToFunctionParameterJS
+			gap = length;
+		}
+		//endregion
+
+		//region Search for sequence
+		const result = this._stream.findFirstSequence(patterns, this._start, length, this.backward);
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS, NonBlockStatementBodyJS, EqualityComparisonWithCoercionJS
+		if (result.value.buffer.byteLength == 0) return result;
+
+		if (this.backward) {
+			if (result.position < this._start - result.value._buffer.byteLength - gap) {
+				return {
+					position: -1,
+					value: new ByteStream()
+				};
+			}
+		} else {
+			if (result.position > this._start + result.value._buffer.byteLength + gap) {
+				return {
+					position: -1,
+					value: new ByteStream()
+				};
+			}
+		}
+		//endregion
+
+		//region Create new values
+		this.start = result.position;
+		//endregion ;
+
+		return result;
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols
+	/**
+  * Find position of a sequence of any patterns from input array
+  * @param {Array.<ByteStream>} patterns Array with patterns which should be found
+  * @returns {Array}
+  */
+	findAllSequences(patterns) {
+		// In case of "backward order" the start position is at the end on stream.
+		// In case of "normal order" the start position is at the begging of the stream.
+		// But in fact for search for all patterns we need to have start position in "normal order".
+		// noinspection ConditionalExpressionJS
+		const start = this.backward ? this.start - this.length : this.start;
+
+		return this.stream.findAllSequences(patterns, start, this.length);
+	}
+	//**********************************************************************************
+	// noinspection JSUnusedGlobalSymbols, FunctionWithMultipleReturnPointsJS
+	/**
+  * Find all paired patterns in the stream
+  * @param {ByteStream} leftPattern Left pattern to search for
+  * @param {ByteStream} rightPattern Right pattern to search for
+  * @param {?number} [gap] Maximum gap between start position and position of nearest object
+  * @returns {Array}
+  */
+	findPairedPatterns(leftPattern, rightPattern, gap = null) {
+		//region Initial variables
+		// noinspection ConstantOnRightSideOfComparisonJS, ConstantOnLeftSideOfComparisonJS
+		if (gap == null || gap > this.length) {
+			// noinspection AssignmentToFunctionParameterJS
+			gap = this.length;
+		}
+		//endregion
+
+		// In case of "bac