···11+# ChangeLog
22+33+## 2.1.1 - 2025-10-24
44+55+### Changed
66+77+- update dev dependencies
88+- fix test suite that was reporting no error with empty responses
99+1010+### Added
1111+1212+- add content length check for BOOLEAN, INTEGER, OID ([GitHub #104](https://github.com/lapo-luchini/asn1js/pull/104))
1313+1414+## 2.1.0 - 2025-08-03
1515+1616+### Changed
1717+1818+- when fields are CHOICEs now both the field name and the choice name are shown (fixes [GitHub #102](https://github.com/lapo-luchini/asn1js/issues/102))
1919+- upgrade minimum NodeJS version supported from 12.20.0 to 14.6.0 due to usage of ?. and ?? operators in defs.js (ECMAScript 2020); older code is still linted against ECMAScript 2015 for now
2020+2121+### Added
2222+2323+- add tests to check expected decoding
2424+2525+## 2.0.6 - 2025-07-29
2626+2727+### Added
2828+2929+- add proper support for standard Base64 (we previously only supported Base64url) (fixes [GitHub #99](https://github.com/lapo-luchini/asn1js/pull/99))
3030+- improve test harness
3131+3232+## 2.0.5 - 2025-04-12
3333+3434+### Added
3535+3636+- add `index-local.html` for local `file://` usage without needing a web server
3737+- add definitions support for `LDAPMessage`
3838+- #TODO continue producing old ChangeLog entries
+5-3
README.md
···101101links
102102-----
103103104104-- [official website](https://lapo.it/asn1js/)
105105-- [dedicated domain](https://asn1js.eu/)
106106-- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
104104+- [official website](https://asn1js.eu/)
105105+- [alternate website](https://lapo.it/asn1js/)
106106+- [single-file version working locally](https://asn1js.eu/index-local.html) (just save this link)
107107+- [InDefero tracker](http://idf.lapo.it/p/asn1js/) (currently offline)
107108- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
109109+- [ChangeLog on GitHub](https://github.com/lapo-luchini/asn1js/blob/trunk/CHANGELOG.md)
108110- [Ohloh code stats](https://www.openhub.net/p/asn1js)
+273-69
asn1.js
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-import { Int10 } from './int10.js';
1716import { oids } from './oids.js';
18171918const
···4241 ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'], // Caron
4342 ];
44434444+/**
4545+ * Truncates a string to a specified length and adds an ellipsis if needed.
4646+ * @param {string} str - The input string to truncate
4747+ * @param {number} len - The maximum length of the string
4848+ * @returns {string} The truncated string
4949+ */
4550function stringCut(str, len) {
4651 if (str.length > len)
4752 str = str.substring(0, len) + ellipsis;
4853 return str;
4954}
50555656+/**
5757+ * Checks if a string contains only printable characters (ASCII 32-126, plus tab, newline, carriage return)
5858+ * @param {string} s - The string to check
5959+ * @throws {Error} If an unprintable character is found
6060+ */
5161function checkPrintable(s) {
5262 let i, v;
5363 for (i = 0; i < s.length; ++i) {
···5767 }
5868}
59696060-/** Class to manage a stream of bytes, with a zero-copy approach.
6161- * It uses an existing array or binary string and advances a position index. */
7070+/**
7171+ * Class to manage a stream of bytes, with a zero-copy approach.
7272+ * It uses an existing array or binary string and advances a position index.
7373+ */
6274export class Stream {
63756476 /**
7777+ * Creates a new Stream object.
6578 * @param {Stream|array|string} enc data (will not be copied)
6679 * @param {?number} pos starting position (mandatory when `end` is not a Stream)
6780 */
···7588 }
7689 if (typeof this.pos != 'number')
7790 throw new Error('"pos" must be a numeric value');
9191+ // Set up the raw byte access function based on the type of data
7892 if (typeof this.enc == 'string')
7993 this.getRaw = pos => this.enc.charCodeAt(pos);
8094 else if (typeof this.enc[0] == 'number')
···8296 else
8397 throw new Error('"enc" must be a numeric array or a string');
8498 }
8585- /** Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
8686- * @param {?number} pos read position if specified, else current position (and increment it) */
9999+100100+ /**
101101+ * Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
102102+ * @param {?number} pos read position if specified, else current position (and increment it)
103103+ * @returns {number} The byte value at the specified position
104104+ */
87105 get(pos) {
88106 if (pos === undefined)
89107 pos = this.pos++;
···91109 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
92110 return this.getRaw(pos);
93111 }
9494- /** Convert a single byte to an hexadcimal string (of length 2).
9595- * @param {number} b */
112112+113113+ /**
114114+ * Convert a single byte to a hexadecimal string (of length 2).
115115+ * @param {number} b - The byte to convert
116116+ * @returns {string} Hexadecimal representation of the byte
117117+ */
96118 static hexByte(b) {
97119 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
98120 }
9999- /** Hexadecimal dump of a specified region of the stream.
100100- * @param {number} start starting position (included)
101101- * @param {number} end ending position (excluded)
102102- * @param {string} type 'raw', 'byte' or 'dump' (default) */
121121+122122+ /**
123123+ * Hexadecimal dump of a specified region of the stream.
124124+ * @param {number} start - starting position (included)
125125+ * @param {number} end - ending position (excluded)
126126+ * @param {string} type - 'raw', 'byte' or 'dump' (default)
127127+ * @returns {string} Hexadecimal representation of the data
128128+ */
103129 hexDump(start, end, type = 'dump') {
104130 let s = '';
105131 for (let i = start; i < end; ++i) {
···115141 }
116142 return s;
117143 }
118118- /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
119119- * @param {number} start starting position (included)
120120- * @param {number} end ending position (excluded)
121121- * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */
144144+145145+ /**
146146+ * Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
147147+ * @param {number} start - starting position (included)
148148+ * @param {number} end - ending position (excluded)
149149+ * @param {string} type - 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
150150+ * @returns {string} Base64 encoded representation of the data
151151+ */
122152 b64Dump(start, end, type = 'url') {
123123- const b64 = type === 'url' ? b64URL : b64Std;
124124- let extra = (end - start) % 3,
125125- s = '',
153153+ const b64 = type === 'url' ? b64URL : b64Std,
154154+ extra = (end - start) % 3;
155155+ let s = '',
126156 i, c;
127157 for (i = start; i + 2 < end; i += 3) {
128158 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
···141171 }
142172 return s;
143173 }
174174+175175+ /**
176176+ * Check if a region of the stream contains only ASCII characters (32-176)
177177+ * @param {number} start - starting position (included)
178178+ * @param {number} end - ending position (excluded)
179179+ * @returns {boolean} True if all characters are ASCII, false otherwise
180180+ */
144181 isASCII(start, end) {
145182 for (let i = start; i < end; ++i) {
146183 let c = this.get(i);
···149186 }
150187 return true;
151188 }
189189+190190+ /**
191191+ * Parse a region of the stream as an ISO string
192192+ * @param {number} start - starting position (included)
193193+ * @param {number} end - ending position (excluded)
194194+ * @param {number} maxLength - maximum length of the output string
195195+ * @returns {Object} Object with size and str properties
196196+ */
152197 parseStringISO(start, end, maxLength) {
153198 let s = '';
154199 for (let i = start; i < end; ++i)
155200 s += String.fromCharCode(this.get(i));
156201 return { size: s.length, str: stringCut(s, maxLength) };
157202 }
203203+204204+ /**
205205+ * Parse a region of the stream as a T.61 string
206206+ * @param {number} start - starting position (included)
207207+ * @param {number} end - ending position (excluded)
208208+ * @param {number} maxLength - maximum length of the output string
209209+ * @returns {Object} Object with size and str properties
210210+ */
158211 parseStringT61(start, end, maxLength) {
159212 // warning: this code is not very well tested so far
160213 function merge(c, d) {
161161- let t = tableT61[c - 0xC0];
162162- let i = t[0].indexOf(String.fromCharCode(d));
214214+ const t = tableT61[c - 0xC0];
215215+ const i = t[0].indexOf(String.fromCharCode(d));
163216 return (i < 0) ? '\0' : t[1].charAt(i);
164217 }
165218 let s = '', c;
···176229 }
177230 return { size: s.length, str: stringCut(s, maxLength) };
178231 }
232232+233233+ /**
234234+ * Parse a region of the stream as a UTF-8 string
235235+ * @param {number} start - starting position (included)
236236+ * @param {number} end - ending position (excluded)
237237+ * @param {number} maxLength - maximum length of the output string
238238+ * @returns {Object} Object with size and str properties
239239+ */
179240 parseStringUTF(start, end, maxLength) {
241241+ /**
242242+ * Helper function to process UTF-8 continuation bytes
243243+ * @param {number} c - The continuation byte
244244+ * @returns {number} The extracted data bits
245245+ */
180246 function ex(c) { // must be 10xxxxxx
181247 if ((c < 0x80) || (c >= 0xC0))
182248 throw new Error('Invalid UTF-8 continuation byte: ' + c);
183249 return (c & 0x3F);
184250 }
251251+ /**
252252+ * Helper function to convert a code point to a surrogate pair
253253+ * @param {number} cp - The code point to convert
254254+ * @returns {string} The surrogate pair as a string
255255+ */
185256 function surrogate(cp) {
186257 if (cp < 0x10000)
187258 throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
···191262 }
192263 let s = '';
193264 for (let i = start; i < end; ) {
194194- let c = this.get(i++);
265265+ const c = this.get(i++);
195266 if (c < 0x80) // 0xxxxxxx (7 bit)
196267 s += String.fromCharCode(c);
197268 else if (c < 0xC0)
···207278 }
208279 return { size: s.length, str: stringCut(s, maxLength) };
209280 }
281281+282282+ /**
283283+ * Parse a region of the stream as a BMP (Basic Multilingual Plane) string
284284+ * @param {number} start - starting position (included)
285285+ * @param {number} end - ending position (excluded)
286286+ * @param {number} maxLength - maximum length of the output string
287287+ * @returns {Object} Object with size and str properties
288288+ */
210289 parseStringBMP(start, end, maxLength) {
211290 let s = '', hi, lo;
212291 for (let i = start; i < end; ) {
···216295 }
217296 return { size: s.length, str: stringCut(s, maxLength) };
218297 }
298298+299299+ /**
300300+ * Parse a region of the stream as a time string
301301+ * @param {number} start - starting position (included)
302302+ * @param {number} end - ending position (excluded)
303303+ * @param {boolean} shortYear - Whether to parse as short year (2-digit)
304304+ * @returns {string} Formatted time string
305305+ */
219306 parseTime(start, end, shortYear) {
220307 let s = this.parseStringISO(start, end).str,
221308 m = (shortYear ? reTimeS : reTimeL).exec(s);
···243330 }
244331 return s;
245332 }
333333+334334+ /**
335335+ * Parse a region of the stream as an integer
336336+ * @param {number} start - starting position (included)
337337+ * @param {number} end - ending position (excluded)
338338+ * @returns {string} Formatted integer string
339339+ */
246340 parseInteger(start, end) {
247341 let v = this.get(start),
248248- neg = (v > 127),
249249- pad = neg ? 255 : 0,
250250- len,
251342 s = '';
343343+ const neg = (v > 127),
344344+ pad = neg ? 255 : 0;
252345 // skip unuseful bits (not allowed in DER)
253346 while (v == pad && ++start < end)
254347 v = this.get(start);
255255- len = end - start;
348348+ const len = end - start;
256349 if (len === 0)
257350 return neg ? '-1' : '0';
258351 // show bit length of huge integers
259352 if (len > 4) {
260260- s = v;
261261- len <<= 3;
262262- while (((s ^ pad) & 0x80) == 0) {
263263- s <<= 1;
264264- --len;
353353+ let v2 = v,
354354+ lenBit = len << 3;
355355+ while (((v2 ^ pad) & 0x80) == 0) {
356356+ v2 <<= 1;
357357+ --lenBit;
265358 }
266266- s = '(' + len + ' bit)\n';
359359+ s = '(' + lenBit + ' bit)\n';
267360 }
268361 // decode the integer
269362 if (neg) v = v - 256;
270270- let n = new Int10(v);
363363+ let n = BigInt(v);
271364 for (let i = start + 1; i < end; ++i)
272272- n.mulAdd(256, this.get(i));
273273- return s + n.toString();
365365+ n = (n << 8n) | BigInt(this.get(i));
366366+ return s + n;
274367 }
368368+369369+ /**
370370+ * Parse a region of the stream as a bit string.
371371+ * @param {number} start - starting position (included)
372372+ * @param {number} end - ending position (excluded)
373373+ * @param {number} maxLength - maximum length of the output string
374374+ * @returns {Object} Object with size and str properties
375375+ */
275376 parseBitString(start, end, maxLength) {
276276- let unusedBits = this.get(start);
377377+ const unusedBits = this.get(start);
277378 if (unusedBits > 7)
278379 throw new Error('Invalid BitString with unusedBits=' + unusedBits);
279279- let lenBit = ((end - start - 1) << 3) - unusedBits,
280280- s = '';
380380+ const lenBit = ((end - start - 1) << 3) - unusedBits;
381381+ let s = '';
281382 for (let i = start + 1; i < end; ++i) {
282383 let b = this.get(i),
283384 skip = (i == end - 1) ? unusedBits : 0;
···288389 }
289390 return { size: lenBit, str: s };
290391 }
392392+393393+ /**
394394+ * Parse a region of the stream as an octet string.
395395+ * @param {number} start - starting position (included)
396396+ * @param {number} end - ending position (excluded)
397397+ * @param {number} maxLength - maximum length of the output string
398398+ * @returns {Object} Object with size and str properties
399399+ */
291400 parseOctetString(start, end, maxLength) {
292292- let len = end - start,
293293- s;
294401 try {
295295- s = this.parseStringUTF(start, end, maxLength);
402402+ let s = this.parseStringUTF(start, end, maxLength);
296403 checkPrintable(s.str);
297404 return { size: end - start, str: s.str };
298405 } catch (ignore) {
299299- // ignore
406406+ // If UTF-8 parsing fails, fall back to hexadecimal dump
300407 }
408408+ const len = end - start;
301409 maxLength /= 2; // we work in bytes
302410 if (len > maxLength)
303411 end = start + maxLength;
304304- s = '';
412412+ let s = '';
305413 for (let i = start; i < end; ++i)
306414 s += Stream.hexByte(this.get(i));
307415 if (len > maxLength)
308416 s += ellipsis;
309417 return { size: len, str: s };
310418 }
419419+420420+ /**
421421+ * Parse a region of the stream as an OID (Object Identifier).
422422+ * @param {number} start - starting position (included)
423423+ * @param {number} end - ending position (excluded)
424424+ * @param {number} maxLength - maximum length of the output string
425425+ * @param {boolean} isRelative - Whether the OID is relative
426426+ * @returns {string} Formatted OID string
427427+ */
311428 parseOID(start, end, maxLength, isRelative) {
312429 let s = '',
313313- n = new Int10(),
430430+ n = 0n,
314431 bits = 0;
315432 for (let i = start; i < end; ++i) {
316433 let v = this.get(i);
317317- n.mulAdd(128, v & 0x7F);
434434+ // Shift bits and add the lower 7 bits of the byte
435435+ n = (n << 7n) | BigInt(v & 0x7F);
318436 bits += 7;
437437+ // If the most significant bit is 0, this is the last byte of the OID component
319438 if (!(v & 0x80)) { // finished
439439+ // If this is the first component, handle it specially
320440 if (s === '') {
321321- n = n.simplify();
322441 if (isRelative) {
323323- s = (n instanceof Int10) ? n.toString() : '' + n;
324324- } else if (n instanceof Int10) {
325325- n.sub(80);
326326- s = '2.' + n.toString();
442442+ s = n.toString();
327443 } else {
328328- let m = n < 80 ? n < 40 ? 0 : 1 : 2;
329329- s = m + '.' + (n - m * 40);
444444+ let m = n < 80 ? n < 40 ? 0n : 1n : 2n;
445445+ s = m + '.' + (n - m * 40n);
330446 }
331447 } else
332332- s += '.' + n.toString();
448448+ s += '.' + n;
333449 if (s.length > maxLength)
334450 return stringCut(s, maxLength);
335335- n = new Int10();
451451+ n = 0n;
336452 bits = 0;
337453 }
338454 }
339455 if (bits > 0)
340456 s += '.incomplete';
457457+ // If OIDs mapping is available and the OID is absolute, try to resolve it
341458 if (typeof oids === 'object' && !isRelative) {
342459 let oid = oids[s];
343460 if (oid) {
···348465 }
349466 return s;
350467 }
468468+469469+ /**
470470+ * Parse a region of the stream as a relative OID (Object Identifier).
471471+ * @param {number} start - starting position (included)
472472+ * @param {number} end - ending position (excluded)
473473+ * @param {number} maxLength - maximum length of the output string
474474+ * @returns {string} Formatted relative OID string
475475+ */
351476 parseRelativeOID(start, end, maxLength) {
352477 return this.parseOID(start, end, maxLength, true);
353478 }
···380505 this.tagConstructed = ((buf & 0x20) !== 0);
381506 this.tagNumber = buf & 0x1F;
382507 if (this.tagNumber == 0x1F) { // long tag
383383- let n = new Int10();
508508+ let n = 0n;
384509 do {
385510 buf = stream.get();
386386- n.mulAdd(128, buf & 0x7F);
511511+ n = (n << 7n) | BigInt(buf & 0x7F);
387512 } while (buf & 0x80);
388388- this.tagNumber = n.simplify();
513513+ this.tagNumber = n <= Number.MAX_SAFE_INTEGER ? Number(n) : n;
389514 }
390515 }
391516 isUniversal() {
···396521 }
397522}
398523524524+/**
525525+ * ASN1 class for parsing ASN.1 encoded data.
526526+ * Instances of this class represent an ASN.1 element and provides methods to parse and display its content.
527527+ */
399528export class ASN1 {
529529+ /**
530530+ * Creates an ASN1 parser object.
531531+ * @param {Stream} stream - The stream containing the ASN.1 data.
532532+ * @param {number} header - The header length.
533533+ * @param {number} length - The length of the data.
534534+ * @param {ASN1Tag} tag - The ASN.1 tag.
535535+ * @param {number} tagLen - The length of the tag.
536536+ * @param {Array} sub - The sub-elements.
537537+ */
400538 constructor(stream, header, length, tag, tagLen, sub) {
401539 if (!(tag instanceof ASN1Tag)) throw new Error('Invalid tag value.');
402540 this.stream = stream;
···406544 this.tagLen = tagLen;
407545 this.sub = sub;
408546 }
547547+548548+ /**
549549+ * Get the type name of the ASN.1 element.
550550+ * @returns {string} The type name.
551551+ */
409552 typeName() {
410553 switch (this.tag.tagClass) {
411554 case 0: // universal
···445588 case 3: return 'Private_' + this.tag.tagNumber.toString();
446589 }
447590 }
448448- /** A string preview of the content (intended for humans). */
591591+592592+ /**
593593+ * Get a string preview of the content (intended for humans).
594594+ * @param {number} maxLength - The maximum length of the content.
595595+ * @returns {string|null} The content preview or null if not supported.
596596+ */
449597 content(maxLength) {
450598 if (this.tag === undefined)
451599 return null;
452600 if (maxLength === undefined)
453601 maxLength = Infinity;
454454- let content = this.posContent(),
602602+ const content = this.posContent(),
455603 len = Math.abs(this.length);
456604 if (!this.tag.isUniversal()) {
457605 if (this.sub !== null)
···461609 }
462610 switch (this.tag.tagNumber) {
463611 case 0x01: // BOOLEAN
612612+ if (len != 1) return 'invalid length ' + len;
464613 return (this.stream.get(content) === 0) ? 'false' : 'true';
465614 case 0x02: // INTEGER
615615+ if (len < 1) return 'invalid length ' + len;
466616 return this.stream.parseInteger(content, content + len);
467617 case 0x03: { // BIT_STRING
468618 let d = recurse(this, 'parseBitString', maxLength);
···474624 }
475625 //case 0x05: // NULL
476626 case 0x06: // OBJECT_IDENTIFIER
627627+ if (len < 1) return 'invalid length ' + len; // pgut001's dumpasn1.c enforces a minimum lenght of 3
477628 return this.stream.parseOID(content, content + len, maxLength);
478629 //case 0x07: // ObjectDescriptor
479630 //case 0x08: // EXTERNAL
···510661 }
511662 return null;
512663 }
664664+665665+ /**
666666+ * Get a string representation of the ASN.1 element.
667667+ * @returns {string} The string representation.
668668+ */
513669 toString() {
514670 return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']';
515671 }
672672+673673+ /**
674674+ * Get a pretty string representation of the ASN.1 element.
675675+ * @param {string} indent - The indentation string.
676676+ * @returns {string} The pretty string representation.
677677+ */
516678 toPrettyString(indent) {
517679 if (indent === undefined) indent = '';
518680 let s = indent;
···543705 }
544706 return s;
545707 }
708708+709709+ /**
710710+ * Get the starting position of the element in the stream.
711711+ * @returns {number} The starting position.
712712+ */
546713 posStart() {
547714 return this.stream.pos;
548715 }
716716+717717+ /**
718718+ * Get the position of the content in the stream.
719719+ * @returns {number} The content position.
720720+ */
549721 posContent() {
550722 return this.stream.pos + this.header;
551723 }
724724+725725+ /**
726726+ * Get the ending position of the element in the stream.
727727+ * @returns {number} The ending position.
728728+ */
552729 posEnd() {
553730 return this.stream.pos + this.header + Math.abs(this.length);
554731 }
555555- /** Position of the length. */
732732+733733+ /**
734734+ * Get the position of the length in the stream.
735735+ * @returns {number} The length position.
736736+ */
556737 posLen() {
557738 return this.stream.pos + this.tagLen;
558739 }
559559- /** Hexadecimal dump of the node.
560560- * @param type 'raw', 'byte' or 'dump' */
740740+741741+ /**
742742+ * Get a hexadecimal dump of the node.
743743+ * @param {string} [type='raw'] - The dump type: 'raw', 'byte', or 'dump'.
744744+ * @returns {string} The hexadecimal dump.
745745+ */
561746 toHexString(type = 'raw') {
562747 return this.stream.hexDump(this.posStart(), this.posEnd(), type);
563748 }
564564- /** Base64url dump of the node (according to RFC 4648 section 5).
565565- * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
566566- */
749749+750750+ /**
751751+ * Get a base64url dump of the node (according to RFC 4648 section 5).
752752+ * @param {string} [type='url'] - The dump type: 'url' (section 5 without padding) or 'std' (section 4 with padding).
753753+ * @returns {string} The base64 encoded representation.
754754+ */
567755 toB64String(type = 'url') {
568756 return this.stream.b64Dump(this.posStart(), this.posEnd(), type);
569757 }
758758+759759+ /**
760760+ * Decode the length field of an ASN.1 element.
761761+ * @param {Stream} stream - The stream to read from.
762762+ * @returns {number|null} The decoded length, or null for indefinite length.
763763+ * @throws {Error} If the length is invalid or exceeds 48 bits.
764764+ */
570765 static decodeLength(stream) {
571571- let buf = stream.get(),
766766+ const buf = stream.get(),
572767 len = buf & 0x7F;
573768 if (len == buf) // first bit was 0, short form
574769 return len;
575770 if (len === 0) // long form with length 0 is a special case
576771 return null; // undefined length
577577- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
772772+ if (len > 6) // no reason to use BigInt, as it would be a huge buffer anyways
578773 throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1));
579579- buf = 0;
774774+ let value = 0;
580775 for (let i = 0; i < len; ++i)
581581- buf = (buf * 256) + stream.get();
582582- return buf;
776776+ value = (value << 8) | stream.get();
777777+ return value;
583778 }
779779+780780+ /**
781781+ * Decode an ASN.1 element from a stream.
782782+ * @param {Stream|array|string} stream - The input data.
783783+ * @param {number} [offset=0] - The offset to start decoding from.
784784+ * @param {Function} [type=ASN1] - The class to instantiate.
785785+ * @returns {ASN1} The decoded ASN.1 element.
786786+ * @throws {Error} If the decoding fails.
787787+ */
584788 static decode(stream, offset, type = ASN1) {
585789 if (!(type == ASN1 || type.prototype instanceof ASN1))
586790 throw new Error('Must pass a class that extends ASN1');
···11-// Big integer base-10 printing library
22-// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
33-44-// Permission to use, copy, modify, and/or distribute this software for any
55-// purpose with or without fee is hereby granted, provided that the above
66-// copyright notice and this permission notice appear in all copies.
77-//
88-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515-1616-/** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */
1717-const max = 10000000000000;
1818-1919-export class Int10 {
2020- /**
2121- * Arbitrary length base-10 value.
2222- * @param {number} value - Optional initial value (will be 0 otherwise).
2323- */
2424- constructor(value) {
2525- this.buf = [+value || 0];
2626- }
2727-2828- /**
2929- * Multiply value by m and add c.
3030- * @param {number} m - multiplier, must be 0<m<=256
3131- * @param {number} c - value to add, must be c>=0
3232- */
3333- mulAdd(m, c) {
3434- // assert(m > 0)
3535- // assert(m <= 256)
3636- // assert(c >= 0)
3737- let b = this.buf,
3838- l = b.length,
3939- i, t;
4040- for (i = 0; i < l; ++i) {
4141- t = b[i] * m + c;
4242- if (t < max)
4343- c = 0;
4444- else {
4545- c = 0|(t / max);
4646- t -= c * max;
4747- }
4848- b[i] = t;
4949- }
5050- if (c > 0)
5151- b[i] = c;
5252- }
5353-5454- /**
5555- * Subtract value.
5656- * @param {number} c - value to subtract
5757- */
5858- sub(c) {
5959- let b = this.buf,
6060- l = b.length,
6161- i, t;
6262- for (i = 0; i < l; ++i) {
6363- t = b[i] - c;
6464- if (t < 0) {
6565- t += max;
6666- c = 1;
6767- } else
6868- c = 0;
6969- b[i] = t;
7070- }
7171- while (b[b.length - 1] === 0)
7272- b.pop();
7373- }
7474-7575- /**
7676- * Convert to decimal string representation.
7777- * @param {number} [base=10] - optional value, only value accepted is 10
7878- * @returns {string} The decimal string representation.
7979- */
8080- toString(base = 10) {
8181- if (base != 10)
8282- throw new Error('only base 10 is supported');
8383- let b = this.buf,
8484- s = b[b.length - 1].toString();
8585- for (let i = b.length - 2; i >= 0; --i)
8686- s += (max + b[i]).toString().substring(1);
8787- return s;
8888- }
8989-9090- /**
9191- * Convert to Number value representation.
9292- * Will probably overflow 2^53 and thus become approximate.
9393- * @returns {number} The numeric value.
9494- */
9595- valueOf() {
9696- let b = this.buf,
9797- v = 0;
9898- for (let i = b.length - 1; i >= 0; --i)
9999- v = v * max + b[i];
100100- return v;
101101- }
102102-103103- /**
104104- * Return value as a simple Number (if it is <= 10000000000000), or return this.
105105- * @returns {number | Int10} The simplified value.
106106- */
107107- simplify() {
108108- let b = this.buf;
109109- return (b.length == 1) ? b[0] : this;
110110- }
111111-112112-}