@penneo/asn1js
Version:
asn1js is a pure JavaScript library implementing this standard. ASN.1 is the basis of all X.509 related data structures and numerous other protocols used on the web
5,750 lines • 168 kB
JavaScript
/* 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.
*
*/
//**************************************************************************************
import { getParametersValue, padNumber, isEqualBuffer, bufferToHexCodes, checkBufferParams, utilToBase, utilFromBase, utilEncodeTC, utilDecodeTC, utilConcatBuf, utilConcatView } from "@penneo/pvutils";
//**************************************************************************************
//region Declaration of global variables
//**************************************************************************************
const powers2 = [new Uint8Array([1])];
const digitsString = "0123456789";
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration for "LocalBaseBlock" class
//**************************************************************************************
/**
* Class used as a base block for all remaining ASN.1 classes
* @typedef LocalBaseBlock
* @interface
* @property {number} blockLength
* @property {string} error
* @property {Array.<string>} warnings
* @property {ArrayBuffer} valueBeforeDecode
*/
class LocalBaseBlock
{
//**********************************************************************************
/**
* Constructor for "LocalBaseBlock" class
* @param {Object} [parameters={}]
* @property {ArrayBuffer} [valueBeforeDecode]
*/
constructor(parameters = {})
{
/**
* @type {number} blockLength
*/
this.blockLength = getParametersValue(parameters, "blockLength", 0);
/**
* @type {string} error
*/
this.error = getParametersValue(parameters, "error", "");
/**
* @type {Array.<string>} warnings
*/
this.warnings = 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: 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 = 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(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 = 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 = getParametersValue(parameters.idBlock, "isHexOnly", false);
this.valueHex = getParametersValue(parameters.idBlock, "valueHex", new ArrayBuffer(0));
//endregion
this.tagClass = getParametersValue(parameters.idBlock, "tagClass", (-1));
this.tagNumber = getParametersValue(parameters.idBlock, "tagNumber", (-1));
this.isConstructed = 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 = 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(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 = 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 = getParametersValue(parameters.lenBlock, "isIndefiniteForm", false);
this.longFormUsed = getParametersValue(parameters.lenBlock, "longFormUsed", false);
this.length = 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(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 = 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 = 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
//**************************************************************************************
export 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 = utilConcatBuf(idBlockBuf, lenBlockBuf);
let valueBlockBuf;
if(sizeOnly === false)
valueBlockBuf = this.valueBlock.toBER(sizeOnly);
else
valueBlockBuf = new ArrayBuffer(this.lenBlock.length);
retBuf = 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 = utilConcatBuf(retBuf, indefBuf);
}
return retBuf;
}
//**********************************************************************************
/**
* Convertion for the block to JSON object
* @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array.<string>, valueBeforeDecode: string}}
*/
toJSON()
{
let object = {};
//region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object
try
{
object = super.toJSON();
}
catch(ex){}
//endregion
object.idBlock = this.idBlock.toJSON();
object.lenBlock = this.lenBlock.toJSON();
object.valueBlock = this.valueBlock.toJSON();
if("name" in this)
object.name = this.name;
if("optional" in this)
object.optional = this.optional;
if("primitiveSchema" in this)
object.primitiveSchema = this.primitiveSchema.toJSON();
return object;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of basic block for all PRIMITIVE types
//**************************************************************************************
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 = 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(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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength);
object.isHexOnly = this.isHexOnly;
return object;
}
//**********************************************************************************
}
//**************************************************************************************
export class Primitive extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "Primitive" class
* @param {Object} [parameters={}]
* @property {ArrayBuffer} [valueHex]
*/
constructor(parameters = {})
{
super(parameters, LocalPrimitiveValueBlock);
this.idBlock.isConstructed = false;
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "PRIMITIVE";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of basic block for all CONSTRUCTED types
//**************************************************************************************
class LocalConstructedValueBlock extends LocalValueBlock
{
//**********************************************************************************
/**
* Constructor for "LocalConstructedValueBlock" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.value = getParametersValue(parameters, "value", []);
this.isIndefiniteForm = 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(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 = 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;
}
//**********************************************************************************
}
//**************************************************************************************
export class Constructed extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "Constructed" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters, LocalConstructedValueBlock);
this.idBlock.isConstructed = true;
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "CONSTRUCTED";
}
//**********************************************************************************
/**
* Base function for converting block from BER encoded array of bytes
* @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
* @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
* @param {!number} inputLength Maximum length of array of bytes which can be using in this function
* @returns {number}
*/
fromBER(inputBuffer, inputOffset, inputLength)
{
this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, (this.lenBlock.isIndefiniteForm === true) ? inputLength : this.lenBlock.length);
if(resultOffset === (-1))
{
this.error = this.valueBlock.error;
return resultOffset;
}
if(this.idBlock.error.length === 0)
this.blockLength += this.idBlock.blockLength;
if(this.lenBlock.error.length === 0)
this.blockLength += this.lenBlock.blockLength;
if(this.valueBlock.error.length === 0)
this.blockLength += this.valueBlock.blockLength;
return resultOffset;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 EndOfContent type class
//**************************************************************************************
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";
}
//**********************************************************************************
}
//**************************************************************************************
export class EndOfContent extends BaseBlock
{
//**********************************************************************************
constructor(paramaters = {})
{
super(paramaters, LocalEndOfContentValueBlock);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 0; // EndOfContent
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "EndOfContent";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Boolean type class
//**************************************************************************************
class LocalBooleanValueBlock extends LocalValueBlock
{
//**********************************************************************************
/**
* Constructor for "LocalBooleanValueBlock" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.value = getParametersValue(parameters, "value", false);
this.isHexOnly = 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(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(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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength);
return object;
}
//**********************************************************************************
}
//**************************************************************************************
export class Boolean extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "Boolean" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters, LocalBooleanValueBlock);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 1; // Boolean
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "Boolean";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Sequence and Set type classes
//**************************************************************************************
export 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";
}
//**********************************************************************************
}
//**************************************************************************************
export class Set extends Constructed
{
//**********************************************************************************
/**
* Constructor for "Set" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 17; // Set
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "Set";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Null type class
//**************************************************************************************
export class Null extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "Null" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters, LocalBaseBlock); // We will not have a call to "Null value block" because of specified "fromBER" and "toBER" functions
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 5; // Null
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "Null";
}
//**********************************************************************************
//noinspection JSUnusedLocalSymbols
/**
* Base function for converting block from BER encoded array of bytes
* @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
* @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
* @param {!number} inputLength Maximum length of array of bytes which can be using in this function
* @returns {number} Offset after least decoded byte
*/
fromBER(inputBuffer, inputOffset, inputLength)
{
if(this.lenBlock.length > 0)
this.warnings.push("Non-zero length of value block for Null type");
if(this.idBlock.error.length === 0)
this.blockLength += this.idBlock.blockLength;
if(this.lenBlock.error.length === 0)
this.blockLength += this.lenBlock.blockLength;
this.blockLength += inputLength;
if((inputOffset + inputLength) > inputBuffer.byteLength)
{
this.error = "End of input reached before message was fully decoded (inconsistent offset and length values)";
return (-1);
}
return (inputOffset + inputLength);
}
//**********************************************************************************
/**
* Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
* @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
* @returns {ArrayBuffer}
*/
toBER(sizeOnly = false)
{
const retBuf = new ArrayBuffer(2);
if(sizeOnly === true)
return retBuf;
const retView = new Uint8Array(retBuf);
retView[0] = 0x05;
retView[1] = 0x00;
return retBuf;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 OctetString type class
//**************************************************************************************
class LocalOctetStringValueBlock extends LocalHexBlock(LocalConstructedValueBlock)
{
//**********************************************************************************
/**
* Constructor for "LocalOctetStringValueBlock" class
* @param {Object} [parameters={}]
* @property {ArrayBuffer} [valueHex]
*/
constructor(parameters = {})
{
super(parameters);
this.isConstructed = 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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength);
return object;
}
//**********************************************************************************
}
//**************************************************************************************
export class OctetString extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "OctetString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters, LocalOctetStringValueBlock);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 4; // OctetString
}
//**********************************************************************************
/**
* Base function for converting block from BER encoded array of bytes
* @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
* @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
* @param {!number} inputLength Maximum length of array of bytes which can be using in this function
* @returns {number} Offset after least decoded byte
*/
fromBER(inputBuffer, inputOffset, inputLength)
{
this.valueBlock.isConstructed = this.idBlock.isConstructed;
this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
//region Ability to encode empty OCTET STRING
if(inputLength === 0)
{
if(this.idBlock.error.length === 0)
this.blockLength += this.idBlock.blockLength;
if(this.lenBlock.error.length === 0)
this.blockLength += this.lenBlock.blockLength;
return inputOffset;
}
//endregion
return super.fromBER(inputBuffer, inputOffset, inputLength);
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "OctetString";
}
//**********************************************************************************
//noinspection JSUnusedGlobalSymbols
/**
* Checking that two OCTETSTRINGs are equal
* @param {OctetString} octetString
*/
isEqual(octetString)
{
//region Check input type
if((octetString instanceof OctetString) === false)
return false;
//endregion
//region Compare two JSON strings
if(JSON.stringify(this) !== JSON.stringify(octetString))
return false;
//endregion
return true;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 BitString type class
//**************************************************************************************
class LocalBitStringValueBlock extends LocalHexBlock(LocalConstructedValueBlock)
{
//**********************************************************************************
/**
* Constructor for "LocalBitStringValueBlock" class
* @param {Object} [parameters={}]
* @property {ArrayBuffer} [valueHex]
*/
constructor(parameters = {})
{
super(parameters);
this.unusedBits = getParametersValue(parameters, "unusedBits", 0);
this.isConstructed = 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(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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength);
return object;
}
//**********************************************************************************
}
//**************************************************************************************
export class BitString extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "BitString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters, LocalBitStringValueBlock);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 3; // BitString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "BitString";
}
//**********************************************************************************
/**
* Base function for converting block from BER encoded array of bytes
* @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
* @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
* @param {!number} inputLength Maximum length of array of bytes which can be using in this function
* @returns {number} Offset after least decoded byte
*/
fromBER(inputBuffer, inputOffset, inputLength)
{
//region Ability to encode empty BitString
if(inputLength === 0)
return inputOffset;
//endregion
this.valueBlock.isConstructed = this.idBlock.isConstructed;
this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
return super.fromBER(inputBuffer, inputOffset, inputLength);
}
//**********************************************************************************
/**
* Checking that two BITSTRINGs are equal
* @param {BitString} bitString
*/
isEqual(bitString)
{
//region Check input type
if((bitString instanceof BitString) === false)
return false;
//endregion
//region Compare two JSON strings
if(JSON.stringify(this) !== JSON.stringify(bitString))
return false;
//endregion
return true;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Integer type class
//**************************************************************************************
/**
* @extends 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 = 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 = 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 = utilConcatView(new Uint8Array([value % 10]), firstViewCopy);
break;
default:
firstViewCopy[firstViewCopyLength - counter] = value % 10;
}
}
if(c[0] > 0)
firstViewCopy = 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 = 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;
}
//**********************************************************************************
}
//**************************************************************************************
export 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 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 isEqualBuffer(this.valueBlock.valueHex, otherValue);
return false;
}
//**********************************************************************************
/**
* Convert current Integer value from BER into DER format
* @returns {Integer}
*/
convertToDER()
{
const integer = new Integer({ valueHex: this.valueBlock.valueHex });
integer.valueBlock.toDER();
return integer;
}
//**********************************************************************************
/**
* Convert current Integer value from DER to BER format
* @returns {Integer}
*/
convertFromDER()
{
const expectedLength = (this.valueBlock.valueHex.byteLength % 2) ? (this.valueBlock.valueHex.byteLength + 1) : this.valueBlock.valueHex.byteLength;
const integer = new Integer({ valueHex: this.valueBlock.valueHex });
integer.valueBlock.fromDER(integer.valueBlock.valueHex, 0, integer.valueBlock.valueHex.byteLength, expectedLength);
return integer;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 Enumerated type class
//**************************************************************************************
export class Enumerated extends Integer
{
//**********************************************************************************
/**
* Constructor for "Enumerated" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 10; // Enumerated
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "Enumerated";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of ASN.1 ObjectIdentifier type class
//**************************************************************************************
class LocalSidValueBlock extends LocalHexBlock(LocalBaseBlock)
{
//**********************************************************************************
/**
* Constructor for "LocalSidValueBlock" class
* @param {Object} [parameters={}]
* @property {number} [valueDec]
* @property {boolean} [isFirstSid]
*/
constructor(parameters = {})
{
super(parameters);
this.valueDec = getParametersValue(parameters, "valueDec", -1);
this.isFirstSid = 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(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 = 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 = 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 = 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(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 = 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
*/
export class ObjectIdentifier extends BaseBlock
{
//**********************************************************************************
/**
* Constructor for "ObjectIdentifier" class
* @param {Object} [parameters={}]
* @property {ArrayBuffer} [valueHex]
*/
constructor(parameters = {})
{
super(parameters, LocalObjectIdentifierValueBlock);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 6; // OBJECT IDENTIFIER
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "ObjectIdentifier";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of all string's classes
//**************************************************************************************
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
*/
export 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;
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @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
*/
export 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 = 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;
}
//**********************************************************************************
}
//**************************************************************************************
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
*/
export 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 = 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;
}
//**********************************************************************************
}
//**************************************************************************************
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
*/
export class NumericString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "NumericString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 18; // NumericString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "NumericString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class PrintableString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "PrintableString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 19; // PrintableString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "PrintableString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class TeletexString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "TeletexString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 20; // TeletexString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "TeletexString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class VideotexString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "VideotexString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 21; // VideotexString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "VideotexString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class IA5String extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "IA5String" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 22; // IA5String
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "IA5String";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class GraphicString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "GraphicString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 25; // GraphicString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "GraphicString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class VisibleString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "VisibleString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 26; // VisibleString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "VisibleString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class GeneralString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "GeneralString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 27; // GeneralString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "GeneralString";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends LocalSimpleStringBlock
*/
export class CharacterString extends LocalSimpleStringBlock
{
//**********************************************************************************
/**
* Constructor for "CharacterString" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 29; // CharacterString
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "CharacterString";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of all date and time classes
//**************************************************************************************
/**
* @extends VisibleString
*/
export 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] = padNumber(((this.year < 2000) ? (this.year - 1900) : (this.year - 2000)), 2);
outputArray[1] = padNumber(this.month, 2);
outputArray[2] = padNumber(this.day, 2);
outputArray[3] = padNumber(this.hour, 2);
outputArray[4] = padNumber(this.minute, 2);
outputArray[5] = padNumber(this.second, 2);
outputArray[6] = "Z";
return outputArray.join("");
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "UTCTime";
}
//**********************************************************************************
/**
* Convertion for the block to JSON object
* @returns {Object}
*/
toJSON()
{
let object = {};
//region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object
try
{
object = super.toJSON();
}
catch(ex){}
//endregion
object.year = this.year;
object.month = this.month;
object.day = this.day;
object.hour = this.hour;
object.minute = this.minute;
object.second = this.second;
return object;
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends VisibleString
*/
export 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(padNumber(this.year, 4));
outputArray.push(padNumber(this.month, 2));
outputArray.push(padNumber(this.day, 2));
outputArray.push(padNumber(this.hour, 2));
outputArray.push(padNumber(this.minute, 2));
outputArray.push(padNumber(this.second, 2));
if(this.millisecond !== 0)
{
outputArray.push(".");
outputArray.push(padNumber(this.millisecond, 3));
}
outputArray.push("Z");
return outputArray.join("");
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "GeneralizedTime";
}
//**********************************************************************************
/**
* Convertion for the block to JSON object
* @returns {Object}
*/
toJSON()
{
let object = {};
//region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object
try
{
object = super.toJSON();
}
catch(ex){}
//endregion
object.year = this.year;
object.month = this.month;
object.day = this.day;
object.hour = this.hour;
object.minute = this.minute;
object.second = this.second;
object.millisecond = this.millisecond;
return object;
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends Utf8String
*/
export class DATE extends Utf8String
{
//**********************************************************************************
/**
* Constructor for "DATE" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 31; // DATE
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "DATE";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends Utf8String
*/
export class TimeOfDay extends Utf8String
{
//**********************************************************************************
/**
* Constructor for "TimeOfDay" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 32; // TimeOfDay
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "TimeOfDay";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends Utf8String
*/
export class DateTime extends Utf8String
{
//**********************************************************************************
/**
* Constructor for "DateTime" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 33; // DateTime
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "DateTime";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends Utf8String
*/
export class Duration extends Utf8String
{
//**********************************************************************************
/**
* Constructor for "Duration" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 34; // Duration
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "Duration";
}
//**********************************************************************************
}
//**************************************************************************************
/**
* @extends Utf8String
*/
export class TIME extends Utf8String
{
//**********************************************************************************
/**
* Constructor for "Time" class
* @param {Object} [parameters={}]
*/
constructor(parameters = {})
{
super(parameters);
this.idBlock.tagClass = 1; // UNIVERSAL
this.idBlock.tagNumber = 14; // Time
}
//**********************************************************************************
/**
* Aux function, need to get a block name. Need to have it here for inhiritence
* @returns {string}
*/
static blockName()
{
return "TIME";
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type Choice
//**************************************************************************************
export 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 = getParametersValue(parameters, "value", []);
this.optional = getParametersValue(parameters, "optional", false);
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type Any
//**************************************************************************************
export class Any
{
//**********************************************************************************
/**
* Constructor for "Any" class
* @param {Object} [parameters={}]
* @property {string} [name]
* @property {boolean} [optional]
*/
constructor(parameters = {})
{
this.name = getParametersValue(parameters, "name", "");
this.optional = getParametersValue(parameters, "optional", false);
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type Repeated
//**************************************************************************************
export class Repeated
{
//**********************************************************************************
/**
* Constructor for "Repeated" class
* @param {Object} [parameters={}]
* @property {string} [name]
* @property {boolean} [optional]
*/
constructor(parameters = {})
{
this.name = getParametersValue(parameters, "name", "");
this.optional = getParametersValue(parameters, "optional", false);
this.value = getParametersValue(parameters, "value", new Any());
this.local = getParametersValue(parameters, "local", false); // Could local or global array to store elements
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Declaration of special ASN.1 schema type RawData
//**************************************************************************************
/**
* @description Special class providing ability to have "toBER/fromBER" for raw ArrayBuffer
*/
export class RawData
{
//**********************************************************************************
/**
* Constructor for "Repeated" class
* @param {Object} [parameters={}]
* @property {string} [name]
* @property {boolean} [optional]
*/
constructor(parameters = {})
{
this.data = getParametersValue(parameters, "data", new ArrayBuffer(0));
}
//**********************************************************************************
/**
* Base function for converting block from BER encoded array of bytes
* @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
* @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
* @param {!number} inputLength Maximum length of array of bytes which can be using in this function
* @returns {number} Offset after least decoded byte
*/
fromBER(inputBuffer, inputOffset, inputLength)
{
this.data = inputBuffer.slice(inputOffset, inputLength);
return (inputOffset + inputLength);
}
//**********************************************************************************
/**
* Encoding of current ASN.1 block into ASN.1 encoded array (BER rules)
* @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes
* @returns {ArrayBuffer}
*/
toBER(sizeOnly = false)
{
return this.data;
}
//**********************************************************************************
}
//**************************************************************************************
//endregion
//**************************************************************************************
//region Major ASN.1 BER decoding function
//**************************************************************************************
/**
* Internal library function for decoding ASN.1 BER
* @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array
* @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started
* @param {!number} inputLength Maximum length of array of bytes which can be using in this function
* @returns {{offset: number, result: Object}}
*/
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(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
*/
export 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}}
*/
export 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}}
*/
export 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
*/
export function fromJSON(json)
{
// TODO Implement
}
//**************************************************************************************
//endregion
//**************************************************************************************