From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/third_party/asn1js/src/schema.ts | 475 ++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 comm/third_party/asn1js/src/schema.ts (limited to 'comm/third_party/asn1js/src/schema.ts') diff --git a/comm/third_party/asn1js/src/schema.ts b/comm/third_party/asn1js/src/schema.ts new file mode 100644 index 0000000000..4f07e92a13 --- /dev/null +++ b/comm/third_party/asn1js/src/schema.ts @@ -0,0 +1,475 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import * as pvtsutils from "pvtsutils"; +import { IS_CONSTRUCTED, EMPTY_STRING, NAME, ID_BLOCK, FROM_BER, TO_BER, TAG_CLASS, TAG_NUMBER, IS_HEX_ONLY, LOCAL, VALUE_HEX_VIEW } from "./internals/constants"; +import { Any } from "./Any"; +import { Choice } from "./Choice"; +import { Repeated } from "./Repeated"; +import { localFromBER } from "./parser"; +import { AsnType, typeStore } from "./TypeStore"; + +export type AsnSchemaType = AsnType | Any | Choice | Repeated; + +export interface CompareSchemaSuccess { + verified: true; + result: AsnType & { [key: string]: any; }; +} + +export interface CompareSchemaFail { + verified: false; + name?: string; + result: AsnType | { error: string; }; +} + +export type CompareSchemaResult = CompareSchemaSuccess | CompareSchemaFail; + +/** + * Compare of two ASN.1 object trees + * @param root Root of input ASN.1 object tree + * @param inputData Input ASN.1 object tree + * @param inputSchema Input ASN.1 schema to compare with + * @return Returns result of comparison + */ +export function compareSchema(root: AsnType, inputData: AsnType, inputSchema: AsnSchemaType): CompareSchemaResult { + //#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) { + return { + verified: true, + result: root + }; + } + } + + if (choiceResult === false) { + const _result: CompareSchemaResult = { + 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 as any)[inputSchema.name] = inputData; // TODO Such call may replace original field of the object (eg idBlock) + + + //#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 ((ID_BLOCK 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 + /// This encoding/decoding is necessary because could be an errors in schema definition + if ((FROM_BER in inputSchema.idBlock) === false) { + return { + verified: false, + result: { error: "Wrong ASN.1 schema" } + }; + } + + if ((TO_BER 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(TAG_CLASS) === 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(TAG_NUMBER) === 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(IS_CONSTRUCTED) === 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 (!(IS_HEX_ONLY in inputSchema.idBlock)) // Since 'isHexOnly' is an inherited 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) { + if ((VALUE_HEX_VIEW in inputSchema.idBlock) === false) // Since 'valueHex' is an inherited property + { + return { + verified: false, + result: { error: "Wrong ASN.1 schema" } + }; + } + + const schemaView = inputSchema.idBlock.valueHexView; + const asn1View = inputData.idBlock.valueHexView; + + 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.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) + (root as any)[inputSchema.name] = inputData; // TODO check field existence. If exists throw an error + } + //#endregion + //#region Getting next ASN.1 block for comparison + if (inputSchema instanceof typeStore.Constructed) { + // TODO not clear how it works for OctetString and BitString + //* if (inputSchema.idBlock.isConstructed) { + let admission = 0; + let result: CompareSchemaResult = { + verified: false, + result: { + error: "Unknown error", + } + }; + + let maxLength = inputSchema.valueBlock.value.length; + + if (maxLength > 0) { + if (inputSchema.valueBlock.value[0] instanceof Repeated) { + // @ts-ignore + maxLength = inputData.valueBlock.value.length; // TODO debug it + } + } + + //#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 + // @ts-ignore + // TODO debug it + 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) { + return { + verified: true, + result: root + }; + } + + //#region Delete early added name of block + if (inputSchema.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) + delete (root as any)[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 + // @ts-ignore + if ((i - admission) >= inputData.valueBlock.value.length) { + if (inputSchema.valueBlock.value[i].optional === false) { + const _result: CompareSchemaResult = { + verified: false, + result: root + }; + + root.error = "Inconsistent length between ASN.1 data and schema"; + + //#region Delete early added name of block + if (inputSchema.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) { + delete (root as any)[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) { + // @ts-ignore + result = compareSchema(root, inputData.valueBlock.value[i], inputSchema.valueBlock.value[0].value); + if (result.verified === false) { + if (inputSchema.valueBlock.value[0].optional) + admission++; + else { + //#region Delete early added name of block + if (inputSchema.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) + delete (root as any)[inputSchema.name]; + } + //#endregion + + return result; + } + } + + if ((NAME in inputSchema.valueBlock.value[0]) && (inputSchema.valueBlock.value[0].name.length > 0)) { + let arrayRoot: Record = {}; + + if ((LOCAL in inputSchema.valueBlock.value[0]) && (inputSchema.valueBlock.value[0].local)) + arrayRoot = inputData; + + else + arrayRoot = root; + + if (typeof arrayRoot[inputSchema.valueBlock.value[0].name] === "undefined") + arrayRoot[inputSchema.valueBlock.value[0].name] = []; + + // @ts-ignore + arrayRoot[inputSchema.valueBlock.value[0].name].push(inputData.valueBlock.value[i]); + } + } + + //#endregion + else { + // @ts-ignore + result = compareSchema(root, inputData.valueBlock.value[i - admission], inputSchema.valueBlock.value[i]); + if (result.verified === false) { + if (inputSchema.valueBlock.value[i].optional) + admission++; + else { + //#region Delete early added name of block + if (inputSchema.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) + delete (root as any)[inputSchema.name]; + } + //#endregion + + return result; + } + } + } + } + } + + if (result.verified === false) // The situation may take place if last element is OPTIONAL and verification failed + { + const _result: CompareSchemaResult = { + verified: false, + result: root + }; + + //#region Delete early added name of block + if (inputSchema.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) { + delete (root as any)[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 (inputSchema.primitiveSchema && + (VALUE_HEX_VIEW in inputData.valueBlock)) { + //#region Decoding of raw ASN.1 data + const asn1 = localFromBER(inputData.valueBlock.valueHexView); + if (asn1.offset === -1) { + const _result: CompareSchemaResult = { + verified: false, + result: asn1.result + }; + + //#region Delete early added name of block + if (inputSchema.name) { + inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING); + if (inputSchema.name) { + delete (root as any)[inputSchema.name]; + _result.name = inputSchema.name; + } + } + //#endregion + + return _result; + } + //#endregion + + return compareSchema(root, asn1.result, inputSchema.primitiveSchema); + } + + return { + verified: true, + result: root + }; + //#endregion +} +/** + * ASN.1 schema verification for ArrayBuffer data + * @param inputBuffer Input BER-encoded ASN.1 data + * @param inputSchema Input ASN.1 schema to verify against to + * @return + */ + +export function verifySchema(inputBuffer: pvtsutils.BufferSource, inputSchema: AsnSchemaType): CompareSchemaResult { + //#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 = localFromBER(pvtsutils.BufferSourceConverter.toUint8Array(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 +} -- cgit v1.2.3