parser.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import * as asn1js from "asn1js";
  2. import { toArrayBuffer } from "@peculiar/utils";
  3. import { AsnPropTypes, AsnTypeTypes } from "./enums.js";
  4. import * as converters from "./converters.js";
  5. import { AsnSchemaValidationError } from "./errors/index.js";
  6. import { isConvertible, isTypeOfArray } from "./helper.js";
  7. import { schemaStorage } from "./storage.js";
  8. export class AsnParser {
  9. static parse(data, target, options) {
  10. const asn1Parsed = asn1js.fromBER(toArrayBuffer(data), options?.berOptions);
  11. if (asn1Parsed.result.error) {
  12. throw new Error(asn1Parsed.result.error);
  13. }
  14. const res = this.fromASN(asn1Parsed.result, target, options);
  15. return res;
  16. }
  17. static fromASN(asn1Schema, target, options) {
  18. try {
  19. if (isConvertible(target)) {
  20. const value = new target();
  21. return value.fromASN(asn1Schema);
  22. }
  23. const schema = schemaStorage.get(target);
  24. schemaStorage.cache(target);
  25. let targetSchema = schema.schema;
  26. const choiceResult = this.handleChoiceTypes(asn1Schema, schema, target, targetSchema, options);
  27. if (choiceResult?.result) {
  28. return choiceResult.result;
  29. }
  30. if (choiceResult?.targetSchema) {
  31. targetSchema = choiceResult.targetSchema;
  32. }
  33. const sequenceResult = this.handleSequenceTypes(asn1Schema, schema, target, targetSchema);
  34. const res = new target();
  35. if (isTypeOfArray(target)) {
  36. return this.handleArrayTypes(asn1Schema, schema, target, options);
  37. }
  38. this.processSchemaItems(schema, sequenceResult, res, options);
  39. return res;
  40. }
  41. catch (error) {
  42. if (error instanceof AsnSchemaValidationError) {
  43. error.schemas.push(target.name);
  44. }
  45. throw error;
  46. }
  47. }
  48. static handleChoiceTypes(asn1Schema, schema, target, targetSchema, options) {
  49. if (asn1Schema.constructor === asn1js.Constructed && schema.type === AsnTypeTypes.Choice && asn1Schema.idBlock.tagClass === 3) {
  50. for (const key in schema.items) {
  51. const schemaItem = schema.items[key];
  52. if (schemaItem.context === asn1Schema.idBlock.tagNumber && schemaItem.implicit) {
  53. if (typeof schemaItem.type === "function" && schemaStorage.has(schemaItem.type)) {
  54. const fieldSchema = schemaStorage.get(schemaItem.type);
  55. if (fieldSchema && fieldSchema.type === AsnTypeTypes.Sequence) {
  56. const newSeq = new asn1js.Sequence();
  57. if ("value" in asn1Schema.valueBlock && Array.isArray(asn1Schema.valueBlock.value) && "value" in newSeq.valueBlock) {
  58. newSeq.valueBlock.value = asn1Schema.valueBlock.value;
  59. const fieldValue = this.fromASN(newSeq, schemaItem.type, options);
  60. const res = new target();
  61. res[key] = fieldValue;
  62. return { result: res };
  63. }
  64. }
  65. }
  66. }
  67. }
  68. }
  69. else if (asn1Schema.constructor === asn1js.Constructed && schema.type !== AsnTypeTypes.Choice) {
  70. const newTargetSchema = new asn1js.Constructed({
  71. idBlock: {
  72. tagClass: 3,
  73. tagNumber: asn1Schema.idBlock.tagNumber,
  74. },
  75. value: schema.schema.valueBlock.value,
  76. });
  77. for (const key in schema.items) {
  78. delete asn1Schema[key];
  79. }
  80. return { targetSchema: newTargetSchema };
  81. }
  82. return null;
  83. }
  84. static handleSequenceTypes(asn1Schema, schema, target, targetSchema) {
  85. if (schema.type === AsnTypeTypes.Sequence) {
  86. const asn1ComparedSchema = asn1js.compareSchema({}, asn1Schema, targetSchema);
  87. if (!asn1ComparedSchema.verified) {
  88. throw new AsnSchemaValidationError(`Data does not match to ${target.name} ASN1 schema.${asn1ComparedSchema.result.error ? ` ${asn1ComparedSchema.result.error}` : ""}`);
  89. }
  90. return asn1ComparedSchema;
  91. }
  92. else {
  93. const asn1ComparedSchema = asn1js.compareSchema({}, asn1Schema, targetSchema);
  94. if (!asn1ComparedSchema.verified) {
  95. throw new AsnSchemaValidationError(`Data does not match to ${target.name} ASN1 schema.${asn1ComparedSchema.result.error ? ` ${asn1ComparedSchema.result.error}` : ""}`);
  96. }
  97. return asn1ComparedSchema;
  98. }
  99. }
  100. static processRepeatedField(asn1Elements, asn1Index, schemaItem) {
  101. let elementsToProcess = asn1Elements.slice(asn1Index);
  102. if (elementsToProcess.length === 1 && elementsToProcess[0].constructor.name === "Sequence") {
  103. const seq = elementsToProcess[0];
  104. if (seq.valueBlock && seq.valueBlock.value && Array.isArray(seq.valueBlock.value)) {
  105. elementsToProcess = seq.valueBlock.value;
  106. }
  107. }
  108. if (typeof schemaItem.type === "number") {
  109. const converter = converters.defaultConverter(schemaItem.type);
  110. if (!converter)
  111. throw new Error(`No converter for ASN.1 type ${schemaItem.type}`);
  112. return elementsToProcess
  113. .filter((el) => el && el.valueBlock)
  114. .map((el) => {
  115. try {
  116. return converter.fromASN(el);
  117. }
  118. catch {
  119. return undefined;
  120. }
  121. })
  122. .filter((v) => v !== undefined);
  123. }
  124. else {
  125. return elementsToProcess
  126. .filter((el) => el && el.valueBlock)
  127. .map((el) => {
  128. try {
  129. return this.fromASN(el, schemaItem.type);
  130. }
  131. catch {
  132. return undefined;
  133. }
  134. })
  135. .filter((v) => v !== undefined);
  136. }
  137. }
  138. static processPrimitiveField(asn1Element, schemaItem) {
  139. const converter = converters.defaultConverter(schemaItem.type);
  140. if (!converter)
  141. throw new Error(`No converter for ASN.1 type ${schemaItem.type}`);
  142. return converter.fromASN(asn1Element);
  143. }
  144. static isOptionalChoiceField(schemaItem) {
  145. return (schemaItem.optional &&
  146. typeof schemaItem.type === "function" &&
  147. schemaStorage.has(schemaItem.type) &&
  148. schemaStorage.get(schemaItem.type).type === AsnTypeTypes.Choice);
  149. }
  150. static processOptionalChoiceField(asn1Element, schemaItem) {
  151. try {
  152. const value = this.fromASN(asn1Element, schemaItem.type);
  153. return {
  154. processed: true,
  155. value,
  156. };
  157. }
  158. catch (err) {
  159. if (err instanceof AsnSchemaValidationError && /Wrong values for Choice type/.test(err.message)) {
  160. return { processed: false };
  161. }
  162. throw err;
  163. }
  164. }
  165. static handleArrayTypes(asn1Schema, schema, target, options) {
  166. if (!("value" in asn1Schema.valueBlock && Array.isArray(asn1Schema.valueBlock.value))) {
  167. throw new Error("Cannot get items from the ASN.1 parsed value. ASN.1 object is not constructed.");
  168. }
  169. const itemType = schema.itemType;
  170. if (typeof itemType === "number") {
  171. const converter = converters.defaultConverter(itemType);
  172. if (!converter) {
  173. throw new Error(`Cannot get default converter for array item of ${target.name} ASN1 schema`);
  174. }
  175. return target.from(asn1Schema.valueBlock.value, (element) => converter.fromASN(element));
  176. }
  177. else {
  178. return target.from(asn1Schema.valueBlock.value, (element) => this.fromASN(element, itemType, options));
  179. }
  180. }
  181. static processSchemaItems(schema, asn1ComparedSchema, res, options) {
  182. for (const key in schema.items) {
  183. const asn1SchemaValue = asn1ComparedSchema.result[key];
  184. if (!asn1SchemaValue) {
  185. continue;
  186. }
  187. const schemaItem = schema.items[key];
  188. const schemaItemType = schemaItem.type;
  189. let parsedValue;
  190. if (typeof schemaItemType === "number" || isConvertible(schemaItemType)) {
  191. parsedValue = this.processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options);
  192. }
  193. else {
  194. parsedValue = this.processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options);
  195. }
  196. if (parsedValue && typeof parsedValue === "object" && "value" in parsedValue && "raw" in parsedValue) {
  197. res[key] = parsedValue.value;
  198. res[`${key}Raw`] = parsedValue.raw;
  199. }
  200. else {
  201. res[key] = parsedValue;
  202. }
  203. }
  204. }
  205. static processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options) {
  206. const converter = schemaItem.converter ?? (isConvertible(schemaItemType) ? new schemaItemType() : null);
  207. if (!converter) {
  208. throw new Error("Converter is empty");
  209. }
  210. if (schemaItem.repeated) {
  211. return this.processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter, options);
  212. }
  213. else {
  214. return this.processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter, options);
  215. }
  216. }
  217. static processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter, options) {
  218. if (schemaItem.implicit) {
  219. const Container = schemaItem.repeated === "sequence" ? asn1js.Sequence : asn1js.Set;
  220. const newItem = new Container();
  221. newItem.valueBlock = asn1SchemaValue.valueBlock;
  222. const newItemAsn = asn1js.fromBER(newItem.toBER(false), options?.berOptions);
  223. if (newItemAsn.offset === -1) {
  224. throw new Error(`Cannot parse the child item. ${newItemAsn.result.error}`);
  225. }
  226. if (!("value" in newItemAsn.result.valueBlock && Array.isArray(newItemAsn.result.valueBlock.value))) {
  227. throw new Error("Cannot get items from the ASN.1 parsed value. ASN.1 object is not constructed.");
  228. }
  229. const value = newItemAsn.result.valueBlock.value;
  230. return Array.from(value, (element) => converter.fromASN(element));
  231. }
  232. else {
  233. return Array.from(asn1SchemaValue, (element) => converter.fromASN(element));
  234. }
  235. }
  236. static processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter, options) {
  237. let value = asn1SchemaValue;
  238. if (schemaItem.implicit) {
  239. let newItem;
  240. if (isConvertible(schemaItemType)) {
  241. newItem = new schemaItemType().toSchema("");
  242. }
  243. else {
  244. const Asn1TypeName = AsnPropTypes[schemaItemType];
  245. const Asn1Type = asn1js[Asn1TypeName];
  246. if (!Asn1Type) {
  247. throw new Error(`Cannot get '${Asn1TypeName}' class from asn1js module`);
  248. }
  249. newItem = new Asn1Type();
  250. }
  251. newItem.valueBlock = value.valueBlock;
  252. value = asn1js.fromBER(newItem.toBER(false), options?.berOptions).result;
  253. }
  254. return converter.fromASN(value);
  255. }
  256. static processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType, options) {
  257. if (schemaItem.repeated) {
  258. if (!Array.isArray(asn1SchemaValue)) {
  259. throw new Error("Cannot get list of items from the ASN.1 parsed value. ASN.1 value should be iterable.");
  260. }
  261. return Array.from(asn1SchemaValue, (element) => this.fromASN(element, schemaItemType, options));
  262. }
  263. else {
  264. const valueToProcess = this.handleImplicitTagging(asn1SchemaValue, schemaItem, schemaItemType);
  265. if (this.isOptionalChoiceField(schemaItem)) {
  266. try {
  267. return this.fromASN(valueToProcess, schemaItemType, options);
  268. }
  269. catch (err) {
  270. if (err instanceof AsnSchemaValidationError && /Wrong values for Choice type/.test(err.message)) {
  271. return undefined;
  272. }
  273. throw err;
  274. }
  275. }
  276. else {
  277. const parsedValue = this.fromASN(valueToProcess, schemaItemType, options);
  278. if (schemaItem.raw) {
  279. return {
  280. value: parsedValue,
  281. raw: asn1SchemaValue.valueBeforeDecodeView,
  282. };
  283. }
  284. return parsedValue;
  285. }
  286. }
  287. }
  288. static handleImplicitTagging(asn1SchemaValue, schemaItem, schemaItemType) {
  289. if (schemaItem.implicit && typeof schemaItem.context === "number") {
  290. const schema = schemaStorage.get(schemaItemType);
  291. if (schema.type === AsnTypeTypes.Sequence) {
  292. const newSeq = new asn1js.Sequence();
  293. if ("value" in asn1SchemaValue.valueBlock && Array.isArray(asn1SchemaValue.valueBlock.value) && "value" in newSeq.valueBlock) {
  294. newSeq.valueBlock.value = asn1SchemaValue.valueBlock.value;
  295. return newSeq;
  296. }
  297. }
  298. else if (schema.type === AsnTypeTypes.Set) {
  299. const newSet = new asn1js.Set();
  300. if ("value" in asn1SchemaValue.valueBlock && Array.isArray(asn1SchemaValue.valueBlock.value) && "value" in newSet.valueBlock) {
  301. newSet.valueBlock.value = asn1SchemaValue.valueBlock.value;
  302. return newSet;
  303. }
  304. }
  305. }
  306. return asn1SchemaValue;
  307. }
  308. }