diff --git a/asn1.js b/asn1.js index 7855118..d157950 100644 --- a/asn1.js +++ b/asn1.js @@ -24,7 +24,26 @@ const oids = require('./oids'), ellipsis = '\u2026', reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/, - reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/; + reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/, + b64Safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', + tableT61 = [ + ['', ''], + ['AEIOUaeiou', 'ÀÈÌÒÙàèìòù'], // Grave + ['ACEILNORSUYZacegilnorsuyz', 'ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź'], // Acute + ['ACEGHIJOSUWYaceghijosuwy', 'ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ'], // Circumflex + ['AINOUainou', 'ÃĨÑÕŨãĩñõũ'], // Tilde + ['AEIOUaeiou', 'ĀĒĪŌŪāēīōū'], // Macron + ['AGUagu', 'ĂĞŬăğŭ'], // Breve + ['CEGIZcegz', 'ĊĖĠİŻċėġż'], // Dot + ['AEIOUYaeiouy', 'ÄËÏÖÜŸäëïöüÿ'], // Umlaut or diæresis + ['', ''], + ['AUau', 'ÅŮåů'], // Ring + ['CGKLNRSTcklnrst', 'ÇĢĶĻŅŖŞŢçķļņŗşţ'], // Cedilla + ['', ''], + ['OUou', 'ŐŰőű'], // Double Acute + ['AEIUaeiu', 'ĄĘĮŲąęįų'], // Ogonek + ['CDELNRSTZcdelnrstz', 'ČĎĚĽŇŘŠŤŽčďěľňřšťž'] // Caron + ]; function stringCut(str, len) { if (str.length > len) @@ -32,226 +51,6 @@ function stringCut(str, len) { return str; } -function Stream(enc, pos) { - if (enc instanceof Stream) { - this.enc = enc.enc; - this.pos = enc.pos; - } else { - // enc should be an array or a binary string - this.enc = enc; - this.pos = pos; - } -} -Stream.prototype.get = function (pos) { - if (pos === undefined) - pos = this.pos++; - if (pos >= this.enc.length) - throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; - return (typeof this.enc == 'string') ? this.enc.charCodeAt(pos) : this.enc[pos]; -}; -Stream.prototype.hexDigits = '0123456789ABCDEF'; -Stream.prototype.hexByte = function (b) { - return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF); -}; -Stream.prototype.hexDump = function (start, end, raw) { - let s = ''; - for (let i = start; i < end; ++i) { - s += this.hexByte(this.get(i)); - if (raw !== true) - switch (i & 0xF) { - case 0x7: s += ' '; break; - case 0xF: s += '\n'; break; - default: s += ' '; - } - } - return s; -}; -const b64Safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; -Stream.prototype.b64Dump = function (start, end) { - let extra = (end - start) % 3, - s = '', - i, c; - for (i = start; i + 2 < end; i += 3) { - c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); - s += b64Safe.charAt(c >> 18 & 0x3F); - s += b64Safe.charAt(c >> 12 & 0x3F); - s += b64Safe.charAt(c >> 6 & 0x3F); - s += b64Safe.charAt(c & 0x3F); - } - if (extra > 0) { - c = this.get(i) << 16; - if (extra > 1) c |= this.get(i + 1) << 8; - s += b64Safe.charAt(c >> 18 & 0x3F); - s += b64Safe.charAt(c >> 12 & 0x3F); - if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F); - } - return s; -}; -Stream.prototype.isASCII = function (start, end) { - for (let i = start; i < end; ++i) { - let c = this.get(i); - if (c < 32 || c > 176) - return false; - } - return true; -}; -Stream.prototype.parseStringISO = function (start, end, maxLength) { - let s = ''; - for (let i = start; i < end; ++i) - s += String.fromCharCode(this.get(i)); - return { size: s.length, str: stringCut(s, maxLength) }; -}; -const tableT61 = [ - ['', ''], - ['AEIOUaeiou', 'ÀÈÌÒÙàèìòù'], // Grave - ['ACEILNORSUYZacegilnorsuyz', 'ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź'], // Acute - ['ACEGHIJOSUWYaceghijosuwy', 'ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ'], // Circumflex - ['AINOUainou', 'ÃĨÑÕŨãĩñõũ'], // Tilde - ['AEIOUaeiou', 'ĀĒĪŌŪāēīōū'], // Macron - ['AGUagu', 'ĂĞŬăğŭ'], // Breve - ['CEGIZcegz', 'ĊĖĠİŻċėġż'], // Dot - ['AEIOUYaeiouy', 'ÄËÏÖÜŸäëïöüÿ'], // Umlaut or diæresis - ['', ''], - ['AUau', 'ÅŮåů'], // Ring - ['CGKLNRSTcklnrst', 'ÇĢĶĻŅŖŞŢçķļņŗşţ'], // Cedilla - ['', ''], - ['OUou', 'ŐŰőű'], // Double Acute - ['AEIUaeiu', 'ĄĘĮŲąęįų'], // Ogonek - ['CDELNRSTZcdelnrstz', 'ČĎĚĽŇŘŠŤŽčďěľňřšťž'] // Caron -]; -Stream.prototype.parseStringT61 = function (start, end, maxLength) { - // warning: this code is not very well tested so far - function merge(c, d) { - let t = tableT61[c - 0xC0]; - let i = t[0].indexOf(String.fromCharCode(d)); - return (i < 0) ? '\0' : t[1].charAt(i); - } - let s = '', c; - for (let i = start; i < end; ++i) { - c = this.get(i); - if (c >= 0xA4 && c <= 0xBF) - s += '$¥#§¤\0\0«\0\0\0\0°±²³×µ¶·÷\0\0»¼½¾¿'.charAt(c - 0xA4); - else if (c >= 0xE0 && c <= 0xFF) - s += 'ΩÆÐªĦ\0IJĿŁØŒºÞŦŊʼnĸæđðħıijŀłøœßþŧŋ\0'.charAt(c - 0xE0); - else if (c >= 0xC0 && c <= 0xCF) - s += merge(c, this.get(++i)); - else // using ISO 8859-1 for characters undefined (or equal) in T.61 - s += String.fromCharCode(c); - } - return { size: s.length, str: stringCut(s, maxLength) }; -}; -Stream.prototype.parseStringUTF = function (start, end, maxLength) { - function ex(c) { // must be 10xxxxxx - if ((c < 0x80) || (c >= 0xC0)) - throw new Error('Invalid UTF-8 continuation byte: ' + c); - return (c & 0x3F); - } - function surrogate(cp) { - if (cp < 0x10000) - throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp); - // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs - cp -= 0x10000; - return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00); - } - let s = ''; - for (let i = start; i < end; ) { - let c = this.get(i++); - if (c < 0x80) // 0xxxxxxx (7 bit) - s += String.fromCharCode(c); - else if (c < 0xC0) - throw new Error('Invalid UTF-8 starting byte: ' + c); - else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit) - s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++))); - else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit) - s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); - else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit) - s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); - else - throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c); - } - return { size: s.length, str: stringCut(s, maxLength) }; -}; -Stream.prototype.parseStringBMP = function (start, end, maxLength) { - let s = '', hi, lo; - for (let i = start; i < end; ) { - hi = this.get(i++); - lo = this.get(i++); - s += String.fromCharCode((hi << 8) | lo); - } - return { size: s.length, str: stringCut(s, maxLength) }; -}; -Stream.prototype.parseTime = function (start, end, shortYear) { - let s = this.parseStringISO(start, end).str, - m = (shortYear ? reTimeS : reTimeL).exec(s); - if (!m) - return 'Unrecognized time: ' + s; - if (shortYear) { - // to avoid querying the timer, use the fixed range [1970, 2069] - // it will conform with ITU X.400 [-10, +40] sliding window until 2030 - m[1] = +m[1]; - m[1] += (m[1] < 70) ? 2000 : 1900; - } - s = m[1] + '-' + m[2] + '-' + m[3] + ' ' + m[4]; - if (m[5]) { - s += ':' + m[5]; - if (m[6]) { - s += ':' + m[6]; - if (m[7]) - s += '.' + m[7]; - } - } - if (m[8]) { - s += ' UTC'; - if (m[9]) - s += m[9] + ':' + (m[10] || '00'); - } - return s; -}; -Stream.prototype.parseInteger = function (start, end) { - let v = this.get(start), - neg = (v > 127), - pad = neg ? 255 : 0, - len, - s = ''; - // skip unuseful bits (not allowed in DER) - while (v == pad && ++start < end) - v = this.get(start); - len = end - start; - if (len === 0) - return neg ? '-1' : '0'; - // show bit length of huge integers - if (len > 4) { - s = v; - len <<= 3; - while (((s ^ pad) & 0x80) == 0) { - s <<= 1; - --len; - } - s = '(' + len + ' bit)\n'; - } - // decode the integer - if (neg) v = v - 256; - let n = new Int10(v); - for (let i = start + 1; i < end; ++i) - n.mulAdd(256, this.get(i)); - return s + n.toString(); -}; -Stream.prototype.parseBitString = function (start, end, maxLength) { - let unusedBits = this.get(start); - if (unusedBits > 7) - throw 'Invalid BitString with unusedBits=' + unusedBits; - let lenBit = ((end - start - 1) << 3) - unusedBits, - s = ''; - for (let i = start + 1; i < end; ++i) { - let b = this.get(i), - skip = (i == end - 1) ? unusedBits : 0; - for (let j = 7; j >= skip; --j) - s += (b >> j) & 1 ? '1' : '0'; - if (s.length > maxLength) - s = stringCut(s, maxLength); - } - return { size: lenBit, str: s }; -}; function checkPrintable(s) { let i, v; for (i = 0; i < s.length; ++i) { @@ -260,118 +59,275 @@ function checkPrintable(s) { throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ')'); } } -Stream.prototype.parseOctetString = function (start, end, maxLength) { - let len = end - start, - s; - try { - s = this.parseStringUTF(start, end, maxLength); - checkPrintable(s.str); - return { size: end - start, str: s.str }; - } catch (e) { - // ignore + +class Stream { + + constructor(enc, pos) { + if (enc instanceof Stream) { + this.enc = enc.enc; + this.pos = enc.pos; + } else { + // enc should be an array or a binary string + this.enc = enc; + this.pos = pos; + } } - maxLength /= 2; // we work in bytes - if (len > maxLength) - end = start + maxLength; - s = ''; - for (let i = start; i < end; ++i) - s += this.hexByte(this.get(i)); - if (len > maxLength) - s += ellipsis; - return { size: len, str: s }; -}; -Stream.prototype.parseOID = function (start, end, maxLength, isRelative) { - let s = '', - n = new Int10(), - bits = 0; - for (let i = start; i < end; ++i) { - let v = this.get(i); - n.mulAdd(128, v & 0x7F); - bits += 7; - if (!(v & 0x80)) { // finished - if (s === '') { - n = n.simplify(); - if (isRelative) { - s = (n instanceof Int10) ? n.toString() : '' + n; - } else if (n instanceof Int10) { - n.sub(80); - s = '2.' + n.toString(); - } else { - let m = n < 80 ? n < 40 ? 0 : 1 : 2; - s = m + '.' + (n - m * 40); + get(pos) { + if (pos === undefined) + pos = this.pos++; + if (pos >= this.enc.length) + throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; + return (typeof this.enc == 'string') ? this.enc.charCodeAt(pos) : this.enc[pos]; + }; + hexDigits = '0123456789ABCDEF'; + hexByte(b) { + return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF); + }; + hexDump(start, end, raw) { + let s = ''; + for (let i = start; i < end; ++i) { + s += this.hexByte(this.get(i)); + if (raw !== true) + switch (i & 0xF) { + case 0x7: s += ' '; break; + case 0xF: s += '\n'; break; + default: s += ' '; } - } else - s += '.' + n.toString(); + } + return s; + }; + b64Dump(start, end) { + let extra = (end - start) % 3, + s = '', + i, c; + for (i = start; i + 2 < end; i += 3) { + c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); + s += b64Safe.charAt(c >> 18 & 0x3F); + s += b64Safe.charAt(c >> 12 & 0x3F); + s += b64Safe.charAt(c >> 6 & 0x3F); + s += b64Safe.charAt(c & 0x3F); + } + if (extra > 0) { + c = this.get(i) << 16; + if (extra > 1) c |= this.get(i + 1) << 8; + s += b64Safe.charAt(c >> 18 & 0x3F); + s += b64Safe.charAt(c >> 12 & 0x3F); + if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F); + } + return s; + }; + isASCII(start, end) { + for (let i = start; i < end; ++i) { + let c = this.get(i); + if (c < 32 || c > 176) + return false; + } + return true; + }; + parseStringISO(start, end, maxLength) { + let s = ''; + for (let i = start; i < end; ++i) + s += String.fromCharCode(this.get(i)); + return { size: s.length, str: stringCut(s, maxLength) }; + }; + parseStringT61(start, end, maxLength) { + // warning: this code is not very well tested so far + function merge(c, d) { + let t = tableT61[c - 0xC0]; + let i = t[0].indexOf(String.fromCharCode(d)); + return (i < 0) ? '\0' : t[1].charAt(i); + } + let s = '', c; + for (let i = start; i < end; ++i) { + c = this.get(i); + if (c >= 0xA4 && c <= 0xBF) + s += '$¥#§¤\0\0«\0\0\0\0°±²³×µ¶·÷\0\0»¼½¾¿'.charAt(c - 0xA4); + else if (c >= 0xE0 && c <= 0xFF) + s += 'ΩÆÐªĦ\0IJĿŁØŒºÞŦŊʼnĸæđðħıijŀłøœßþŧŋ\0'.charAt(c - 0xE0); + else if (c >= 0xC0 && c <= 0xCF) + s += merge(c, this.get(++i)); + else // using ISO 8859-1 for characters undefined (or equal) in T.61 + s += String.fromCharCode(c); + } + return { size: s.length, str: stringCut(s, maxLength) }; + }; + parseStringUTF(start, end, maxLength) { + function ex(c) { // must be 10xxxxxx + if ((c < 0x80) || (c >= 0xC0)) + throw new Error('Invalid UTF-8 continuation byte: ' + c); + return (c & 0x3F); + } + function surrogate(cp) { + if (cp < 0x10000) + throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp); + // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs + cp -= 0x10000; + return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00); + } + let s = ''; + for (let i = start; i < end; ) { + let c = this.get(i++); + if (c < 0x80) // 0xxxxxxx (7 bit) + s += String.fromCharCode(c); + else if (c < 0xC0) + throw new Error('Invalid UTF-8 starting byte: ' + c); + else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit) + s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++))); + else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit) + s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); + else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit) + s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); + else + throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c); + } + return { size: s.length, str: stringCut(s, maxLength) }; + }; + parseStringBMP(start, end, maxLength) { + let s = '', hi, lo; + for (let i = start; i < end; ) { + hi = this.get(i++); + lo = this.get(i++); + s += String.fromCharCode((hi << 8) | lo); + } + return { size: s.length, str: stringCut(s, maxLength) }; + }; + parseTime(start, end, shortYear) { + let s = this.parseStringISO(start, end).str, + m = (shortYear ? reTimeS : reTimeL).exec(s); + if (!m) + return 'Unrecognized time: ' + s; + if (shortYear) { + // to avoid querying the timer, use the fixed range [1970, 2069] + // it will conform with ITU X.400 [-10, +40] sliding window until 2030 + m[1] = +m[1]; + m[1] += (m[1] < 70) ? 2000 : 1900; + } + s = m[1] + '-' + m[2] + '-' + m[3] + ' ' + m[4]; + if (m[5]) { + s += ':' + m[5]; + if (m[6]) { + s += ':' + m[6]; + if (m[7]) + s += '.' + m[7]; + } + } + if (m[8]) { + s += ' UTC'; + if (m[9]) + s += m[9] + ':' + (m[10] || '00'); + } + return s; + }; + parseInteger(start, end) { + let v = this.get(start), + neg = (v > 127), + pad = neg ? 255 : 0, + len, + s = ''; + // skip unuseful bits (not allowed in DER) + while (v == pad && ++start < end) + v = this.get(start); + len = end - start; + if (len === 0) + return neg ? '-1' : '0'; + // show bit length of huge integers + if (len > 4) { + s = v; + len <<= 3; + while (((s ^ pad) & 0x80) == 0) { + s <<= 1; + --len; + } + s = '(' + len + ' bit)\n'; + } + // decode the integer + if (neg) v = v - 256; + let n = new Int10(v); + for (let i = start + 1; i < end; ++i) + n.mulAdd(256, this.get(i)); + return s + n.toString(); + }; + parseBitString(start, end, maxLength) { + let unusedBits = this.get(start); + if (unusedBits > 7) + throw 'Invalid BitString with unusedBits=' + unusedBits; + let lenBit = ((end - start - 1) << 3) - unusedBits, + s = ''; + for (let i = start + 1; i < end; ++i) { + let b = this.get(i), + skip = (i == end - 1) ? unusedBits : 0; + for (let j = 7; j >= skip; --j) + s += (b >> j) & 1 ? '1' : '0'; if (s.length > maxLength) - return stringCut(s, maxLength); - n = new Int10(); + s = stringCut(s, maxLength); + } + return { size: lenBit, str: s }; + }; + parseOctetString(start, end, maxLength) { + let len = end - start, + s; + try { + s = this.parseStringUTF(start, end, maxLength); + checkPrintable(s.str); + return { size: end - start, str: s.str }; + } catch (e) { + // ignore + } + maxLength /= 2; // we work in bytes + if (len > maxLength) + end = start + maxLength; + s = ''; + for (let i = start; i < end; ++i) + s += this.hexByte(this.get(i)); + if (len > maxLength) + s += ellipsis; + return { size: len, str: s }; + }; + parseOID(start, end, maxLength, isRelative) { + let s = '', + n = new Int10(), bits = 0; + for (let i = start; i < end; ++i) { + let v = this.get(i); + n.mulAdd(128, v & 0x7F); + bits += 7; + if (!(v & 0x80)) { // finished + if (s === '') { + n = n.simplify(); + if (isRelative) { + s = (n instanceof Int10) ? n.toString() : '' + n; + } else if (n instanceof Int10) { + n.sub(80); + s = '2.' + n.toString(); + } else { + let m = n < 80 ? n < 40 ? 0 : 1 : 2; + s = m + '.' + (n - m * 40); + } + } else + s += '.' + n.toString(); + if (s.length > maxLength) + return stringCut(s, maxLength); + n = new Int10(); + bits = 0; + } } - } - if (bits > 0) - s += '.incomplete'; - if (typeof oids === 'object' && !isRelative) { - let oid = oids[s]; - if (oid) { - if (oid.d) s += '\n' + oid.d; - if (oid.c) s += '\n' + oid.c; - if (oid.w) s += '\n(warning!)'; + if (bits > 0) + s += '.incomplete'; + if (typeof oids === 'object' && !isRelative) { + let oid = oids[s]; + if (oid) { + if (oid.d) s += '\n' + oid.d; + if (oid.c) s += '\n' + oid.c; + if (oid.w) s += '\n(warning!)'; + } } - } - return s; -}; -Stream.prototype.parseRelativeOID = function (start, end, maxLength) { - return this.parseOID(start, end, maxLength, true); -}; - -function ASN1(stream, header, length, tag, tagLen, sub) { - if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.'; - this.stream = stream; - this.header = header; - this.length = length; - this.tag = tag; - this.tagLen = tagLen; - this.sub = sub; + return s; + }; + parseRelativeOID(start, end, maxLength) { + return this.parseOID(start, end, maxLength, true); + }; } -ASN1.prototype.typeName = function () { - switch (this.tag.tagClass) { - case 0: // universal - switch (this.tag.tagNumber) { - case 0x00: return 'EOC'; - case 0x01: return 'BOOLEAN'; - case 0x02: return 'INTEGER'; - case 0x03: return 'BIT_STRING'; - case 0x04: return 'OCTET_STRING'; - case 0x05: return 'NULL'; - case 0x06: return 'OBJECT_IDENTIFIER'; - case 0x07: return 'ObjectDescriptor'; - case 0x08: return 'EXTERNAL'; - case 0x09: return 'REAL'; - case 0x0A: return 'ENUMERATED'; - case 0x0B: return 'EMBEDDED_PDV'; - case 0x0C: return 'UTF8String'; - case 0x0D: return 'RELATIVE_OID'; - case 0x10: return 'SEQUENCE'; - case 0x11: return 'SET'; - case 0x12: return 'NumericString'; - case 0x13: return 'PrintableString'; // ASCII subset - case 0x14: return 'TeletexString'; // aka T61String - case 0x15: return 'VideotexString'; - case 0x16: return 'IA5String'; // ASCII - case 0x17: return 'UTCTime'; - case 0x18: return 'GeneralizedTime'; - case 0x19: return 'GraphicString'; - case 0x1A: return 'VisibleString'; // ASCII subset - case 0x1B: return 'GeneralString'; - case 0x1C: return 'UniversalString'; - case 0x1E: return 'BMPString'; - } - return 'Universal_' + this.tag.tagNumber.toString(); - case 1: return 'Application_' + this.tag.tagNumber.toString(); - case 2: return '[' + this.tag.tagNumber.toString() + ']'; // Context - case 3: return 'Private_' + this.tag.tagNumber.toString(); - } -}; + function recurse(el, parser, maxLength) { let avoidRecurse = true; if (el.tag.tagConstructed && el.sub) { @@ -391,210 +347,265 @@ function recurse(el, parser, maxLength) { }); return d; } -/** A string preview of the content (intended for humans). */ -ASN1.prototype.content = function (maxLength) { - if (this.tag === undefined) - return null; - if (maxLength === undefined) - maxLength = Infinity; - let content = this.posContent(), - len = Math.abs(this.length); - if (!this.tag.isUniversal()) { - if (this.sub !== null) - return '(' + this.sub.length + ' elem)'; - let d1 = this.stream.parseOctetString(content, content + len, maxLength); - return '(' + d1.size + ' byte)\n' + d1.str; - } - switch (this.tag.tagNumber) { - case 0x01: // BOOLEAN - return (this.stream.get(content) === 0) ? 'false' : 'true'; - case 0x02: // INTEGER - return this.stream.parseInteger(content, content + len); - case 0x03: { // BIT_STRING - let d = recurse(this, 'parseBitString', maxLength); - return '(' + d.size + ' bit)\n' + d.str; - } - case 0x04: { // OCTET_STRING - let d = recurse(this, 'parseOctetString', maxLength); - return '(' + d.size + ' byte)\n' + d.str; - } - //case 0x05: // NULL - case 0x06: // OBJECT_IDENTIFIER - return this.stream.parseOID(content, content + len, maxLength); - //case 0x07: // ObjectDescriptor - //case 0x08: // EXTERNAL - //case 0x09: // REAL - case 0x0A: // ENUMERATED - return this.stream.parseInteger(content, content + len); - //case 0x0B: // EMBEDDED_PDV - case 0x0D: // RELATIVE-OID - return this.stream.parseRelativeOID(content, content + len, maxLength); - case 0x10: // SEQUENCE - case 0x11: // SET - if (this.sub !== null) - return '(' + this.sub.length + ' elem)'; - else - return '(no elem)'; - case 0x0C: // UTF8String - return recurse(this, 'parseStringUTF', maxLength).str; - case 0x14: // TeletexString - return recurse(this, 'parseStringT61', maxLength).str; - case 0x12: // NumericString - case 0x13: // PrintableString - case 0x15: // VideotexString - case 0x16: // IA5String - case 0x1A: // VisibleString - case 0x1B: // GeneralString - //case 0x19: // GraphicString - //case 0x1C: // UniversalString - return recurse(this, 'parseStringISO', maxLength).str; - case 0x1E: // BMPString - return recurse(this, 'parseStringBMP', maxLength).str; - case 0x17: // UTCTime - case 0x18: // GeneralizedTime - return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17)); - } - return null; -}; -ASN1.prototype.toString = function () { - return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']'; -}; -ASN1.prototype.toPrettyString = function (indent) { - if (indent === undefined) indent = ''; - let s = indent + this.typeName() + ' @' + this.stream.pos; - if (this.length >= 0) - s += '+'; - s += this.length; - if (this.tag.tagConstructed) - s += ' (constructed)'; - else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) - s += ' (encapsulates)'; - let content = this.content(); - if (content) - s += ': ' + content.replace(/\n/g, '|'); - s += '\n'; - if (this.sub !== null) { - indent += ' '; - for (let i = 0, max = this.sub.length; i < max; ++i) - s += this.sub[i].toPrettyString(indent); - } - return s; -}; -ASN1.prototype.posStart = function () { - return this.stream.pos; -}; -ASN1.prototype.posContent = function () { - return this.stream.pos + this.header; -}; -ASN1.prototype.posEnd = function () { - return this.stream.pos + this.header + Math.abs(this.length); -}; -/** Position of the length. */ -ASN1.prototype.posLen = function() { - return this.stream.pos + this.tagLen; -}; -ASN1.prototype.toHexString = function () { - return this.stream.hexDump(this.posStart(), this.posEnd(), true); -}; -ASN1.prototype.toB64String = function () { - return this.stream.b64Dump(this.posStart(), this.posEnd()); -}; -ASN1.decodeLength = function (stream) { - let buf = stream.get(), - len = buf & 0x7F; - if (len == buf) // first bit was 0, short form - return len; - if (len === 0) // long form with length 0 is a special case - return null; // undefined length - if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways - throw 'Length over 48 bits not supported at position ' + (stream.pos - 1); - buf = 0; - for (let i = 0; i < len; ++i) - buf = (buf * 256) + stream.get(); - return buf; -}; -function ASN1Tag(stream) { - let buf = stream.get(); - this.tagClass = buf >> 6; - this.tagConstructed = ((buf & 0x20) !== 0); - this.tagNumber = buf & 0x1F; - if (this.tagNumber == 0x1F) { // long tag - let n = new Int10(); - do { - buf = stream.get(); - n.mulAdd(128, buf & 0x7F); - } while (buf & 0x80); - this.tagNumber = n.simplify(); + +class ASN1 { + constructor(stream, header, length, tag, tagLen, sub) { + if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.'; + this.stream = stream; + this.header = header; + this.length = length; + this.tag = tag; + this.tagLen = tagLen; + this.sub = sub; } -} -ASN1Tag.prototype.isUniversal = function () { - return this.tagClass === 0x00; -}; -ASN1Tag.prototype.isEOC = function () { - return this.tagClass === 0x00 && this.tagNumber === 0x00; -}; -ASN1.decode = function (stream, offset) { - if (!(stream instanceof Stream)) - stream = new Stream(stream, offset || 0); - let streamStart = new Stream(stream), - tag = new ASN1Tag(stream), - tagLen = stream.pos - streamStart.pos, - len = ASN1.decodeLength(stream), - start = stream.pos, - header = start - streamStart.pos, - sub = null, - getSub = function () { - sub = []; - if (len !== null) { - // definite length - let end = start + len; - if (end > stream.enc.length) - throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream'; - while (stream.pos < end) - sub[sub.length] = ASN1.decode(stream); - if (stream.pos != end) - throw 'Content size is not correct for container at offset ' + start; - } else { - // undefined length - try { - for (;;) { - let s = ASN1.decode(stream); - if (s.tag.isEOC()) - break; - sub[sub.length] = s; + typeName() { + switch (this.tag.tagClass) { + case 0: // universal + switch (this.tag.tagNumber) { + case 0x00: return 'EOC'; + case 0x01: return 'BOOLEAN'; + case 0x02: return 'INTEGER'; + case 0x03: return 'BIT_STRING'; + case 0x04: return 'OCTET_STRING'; + case 0x05: return 'NULL'; + case 0x06: return 'OBJECT_IDENTIFIER'; + case 0x07: return 'ObjectDescriptor'; + case 0x08: return 'EXTERNAL'; + case 0x09: return 'REAL'; + case 0x0A: return 'ENUMERATED'; + case 0x0B: return 'EMBEDDED_PDV'; + case 0x0C: return 'UTF8String'; + case 0x0D: return 'RELATIVE_OID'; + case 0x10: return 'SEQUENCE'; + case 0x11: return 'SET'; + case 0x12: return 'NumericString'; + case 0x13: return 'PrintableString'; // ASCII subset + case 0x14: return 'TeletexString'; // aka T61String + case 0x15: return 'VideotexString'; + case 0x16: return 'IA5String'; // ASCII + case 0x17: return 'UTCTime'; + case 0x18: return 'GeneralizedTime'; + case 0x19: return 'GraphicString'; + case 0x1A: return 'VisibleString'; // ASCII subset + case 0x1B: return 'GeneralString'; + case 0x1C: return 'UniversalString'; + case 0x1E: return 'BMPString'; + } + return 'Universal_' + this.tag.tagNumber.toString(); + case 1: return 'Application_' + this.tag.tagNumber.toString(); + case 2: return '[' + this.tag.tagNumber.toString() + ']'; // Context + case 3: return 'Private_' + this.tag.tagNumber.toString(); + } + }; + /** A string preview of the content (intended for humans). */ + content(maxLength) { + if (this.tag === undefined) + return null; + if (maxLength === undefined) + maxLength = Infinity; + let content = this.posContent(), + len = Math.abs(this.length); + if (!this.tag.isUniversal()) { + if (this.sub !== null) + return '(' + this.sub.length + ' elem)'; + let d1 = this.stream.parseOctetString(content, content + len, maxLength); + return '(' + d1.size + ' byte)\n' + d1.str; + } + switch (this.tag.tagNumber) { + case 0x01: // BOOLEAN + return (this.stream.get(content) === 0) ? 'false' : 'true'; + case 0x02: // INTEGER + return this.stream.parseInteger(content, content + len); + case 0x03: { // BIT_STRING + let d = recurse(this, 'parseBitString', maxLength); + return '(' + d.size + ' bit)\n' + d.str; + } + case 0x04: { // OCTET_STRING + let d = recurse(this, 'parseOctetString', maxLength); + return '(' + d.size + ' byte)\n' + d.str; + } + //case 0x05: // NULL + case 0x06: // OBJECT_IDENTIFIER + return this.stream.parseOID(content, content + len, maxLength); + //case 0x07: // ObjectDescriptor + //case 0x08: // EXTERNAL + //case 0x09: // REAL + case 0x0A: // ENUMERATED + return this.stream.parseInteger(content, content + len); + //case 0x0B: // EMBEDDED_PDV + case 0x0D: // RELATIVE-OID + return this.stream.parseRelativeOID(content, content + len, maxLength); + case 0x10: // SEQUENCE + case 0x11: // SET + if (this.sub !== null) + return '(' + this.sub.length + ' elem)'; + else + return '(no elem)'; + case 0x0C: // UTF8String + return recurse(this, 'parseStringUTF', maxLength).str; + case 0x14: // TeletexString + return recurse(this, 'parseStringT61', maxLength).str; + case 0x12: // NumericString + case 0x13: // PrintableString + case 0x15: // VideotexString + case 0x16: // IA5String + case 0x1A: // VisibleString + case 0x1B: // GeneralString + //case 0x19: // GraphicString + //case 0x1C: // UniversalString + return recurse(this, 'parseStringISO', maxLength).str; + case 0x1E: // BMPString + return recurse(this, 'parseStringBMP', maxLength).str; + case 0x17: // UTCTime + case 0x18: // GeneralizedTime + return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17)); + } + return null; + }; + toString() { + return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']'; + }; + toPrettyString(indent) { + if (indent === undefined) indent = ''; + let s = indent + this.typeName() + ' @' + this.stream.pos; + if (this.length >= 0) + s += '+'; + s += this.length; + if (this.tag.tagConstructed) + s += ' (constructed)'; + else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) + s += ' (encapsulates)'; + let content = this.content(); + if (content) + s += ': ' + content.replace(/\n/g, '|'); + s += '\n'; + if (this.sub !== null) { + indent += ' '; + for (let i = 0, max = this.sub.length; i < max; ++i) + s += this.sub[i].toPrettyString(indent); + } + return s; + }; + posStart() { + return this.stream.pos; + }; + posContent() { + return this.stream.pos + this.header; + }; + posEnd() { + return this.stream.pos + this.header + Math.abs(this.length); + }; + /** Position of the length. */ + posLen() { + return this.stream.pos + this.tagLen; + }; + toHexString() { + return this.stream.hexDump(this.posStart(), this.posEnd(), true); + }; + toB64String() { + return this.stream.b64Dump(this.posStart(), this.posEnd()); + }; + static decodeLength(stream) { + let buf = stream.get(), + len = buf & 0x7F; + if (len == buf) // first bit was 0, short form + return len; + if (len === 0) // long form with length 0 is a special case + return null; // undefined length + if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways + throw 'Length over 48 bits not supported at position ' + (stream.pos - 1); + buf = 0; + for (let i = 0; i < len; ++i) + buf = (buf * 256) + stream.get(); + return buf; + }; + static decode(stream, offset) { + if (!(stream instanceof Stream)) + stream = new Stream(stream, offset || 0); + let streamStart = new Stream(stream), + tag = new ASN1Tag(stream), + tagLen = stream.pos - streamStart.pos, + len = ASN1.decodeLength(stream), + start = stream.pos, + header = start - streamStart.pos, + sub = null, + getSub = function () { + sub = []; + if (len !== null) { + // definite length + let end = start + len; + if (end > stream.enc.length) + throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream'; + while (stream.pos < end) + sub[sub.length] = ASN1.decode(stream); + if (stream.pos != end) + throw 'Content size is not correct for container at offset ' + start; + } else { + // undefined length + try { + for (;;) { + let s = ASN1.decode(stream); + if (s.tag.isEOC()) + break; + sub[sub.length] = s; + } + len = start - stream.pos; // undefined lengths are represented as negative values + } catch (e) { + throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e; } - len = start - stream.pos; // undefined lengths are represented as negative values - } catch (e) { - throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e; } - } - }; - if (tag.tagConstructed) { - // must have valid content - getSub(); - } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) { - // sometimes BitString and OctetString are used to encapsulate ASN.1 - try { - if (tag.tagNumber == 0x03) - if (stream.get() != 0) - throw 'BIT STRINGs with unused bits cannot encapsulate.'; + }; + if (tag.tagConstructed) { + // must have valid content getSub(); - for (let i = 0; i < sub.length; ++i) - if (sub[i].tag.isEOC()) - throw 'EOC is not supposed to be actual content.'; - } catch (e) { - // but silently ignore when they don't - sub = null; - //DEBUG console.log('Could not decode structure at ' + start + ':', e); + } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) { + // sometimes BitString and OctetString are used to encapsulate ASN.1 + try { + if (tag.tagNumber == 0x03) + if (stream.get() != 0) + throw 'BIT STRINGs with unused bits cannot encapsulate.'; + getSub(); + for (let i = 0; i < sub.length; ++i) + if (sub[i].tag.isEOC()) + throw 'EOC is not supposed to be actual content.'; + } catch (e) { + // but silently ignore when they don't + sub = null; + //DEBUG console.log('Could not decode structure at ' + start + ':', e); + } + } + if (sub === null) { + if (len === null) + throw "We can't skip over an invalid tag with undefined length at offset " + start; + stream.pos = start + Math.abs(len); + } + return new ASN1(streamStart, header, len, tag, tagLen, sub); + }; + +} + +class ASN1Tag { + constructor(stream) { + let buf = stream.get(); + this.tagClass = buf >> 6; + this.tagConstructed = ((buf & 0x20) !== 0); + this.tagNumber = buf & 0x1F; + if (this.tagNumber == 0x1F) { // long tag + let n = new Int10(); + do { + buf = stream.get(); + n.mulAdd(128, buf & 0x7F); + } while (buf & 0x80); + this.tagNumber = n.simplify(); } } - if (sub === null) { - if (len === null) - throw "We can't skip over an invalid tag with undefined length at offset " + start; - stream.pos = start + Math.abs(len); - } - return new ASN1(streamStart, header, len, tag, tagLen, sub); -}; + isUniversal() { + return this.tagClass === 0x00; + }; + isEOC() { + return this.tagClass === 0x00 && this.tagNumber === 0x00; + }; +} return ASN1; diff --git a/base64.js b/base64.js index fb9892e..7208f5b 100644 --- a/base64.js +++ b/base64.js @@ -20,89 +20,92 @@ 'use strict'; const - Base64 = {}, haveU8 = (typeof Uint8Array == 'function'); let decoder; // populated on first usage -Base64.decode = function (a) { - let isString = (typeof a == 'string'); - let i; - if (decoder === undefined) { - let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', - ignore = '= \f\n\r\t\u00A0\u2028\u2029'; - decoder = []; - for (i = 0; i < 64; ++i) - decoder[b64.charCodeAt(i)] = i; - for (i = 0; i < ignore.length; ++i) - decoder[ignore.charCodeAt(i)] = -1; - // RFC 3548 URL & file safe encoding - decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; - decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; - } - let out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : []; - let bits = 0, char_count = 0, len = 0; - for (i = 0; i < a.length; ++i) { - let c = isString ? a.charCodeAt(i) : a[i]; - if (c == 61) // '='.charCodeAt(0) +class Base64 { + + static decode(a) { + let isString = (typeof a == 'string'); + let i; + if (decoder === undefined) { + let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + ignore = '= \f\n\r\t\u00A0\u2028\u2029'; + decoder = []; + for (i = 0; i < 64; ++i) + decoder[b64.charCodeAt(i)] = i; + for (i = 0; i < ignore.length; ++i) + decoder[ignore.charCodeAt(i)] = -1; + // RFC 3548 URL & file safe encoding + decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; + decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; + } + let out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : []; + let bits = 0, char_count = 0, len = 0; + for (i = 0; i < a.length; ++i) { + let c = isString ? a.charCodeAt(i) : a[i]; + if (c == 61) // '='.charCodeAt(0) + break; + c = decoder[c]; + if (c == -1) + continue; + if (c === undefined) + throw 'Illegal character at offset ' + i; + bits |= c; + if (++char_count >= 4) { + out[len++] = (bits >> 16); + out[len++] = (bits >> 8) & 0xFF; + out[len++] = bits & 0xFF; + bits = 0; + char_count = 0; + } else { + bits <<= 6; + } + } + switch (char_count) { + case 1: + throw 'Base64 encoding incomplete: at least 2 bits missing'; + case 2: + out[len++] = (bits >> 10); break; - c = decoder[c]; - if (c == -1) - continue; - if (c === undefined) - throw 'Illegal character at offset ' + i; - bits |= c; - if (++char_count >= 4) { + case 3: out[len++] = (bits >> 16); out[len++] = (bits >> 8) & 0xFF; - out[len++] = bits & 0xFF; - bits = 0; - char_count = 0; - } else { - bits <<= 6; + break; } - } - switch (char_count) { - case 1: - throw 'Base64 encoding incomplete: at least 2 bits missing'; - case 2: - out[len++] = (bits >> 10); - break; - case 3: - out[len++] = (bits >> 16); - out[len++] = (bits >> 8) & 0xFF; - break; - } - if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters - out = out.subarray(0, len); - return out; -}; + if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters + out = out.subarray(0, len); + return out; + }; + + static pretty(str) { + // fix padding + if (str.length % 4 > 0) + str = (str + '===').slice(0, str.length + str.length % 4); + // convert RFC 3548 to standard Base64 + str = str.replace(/-/g, '+').replace(/_/g, '/'); + // 80 column width + return str.replace(/(.{80})/g, '$1\n'); + }; -Base64.pretty = function (str) { - // fix padding - if (str.length % 4 > 0) - str = (str + '===').slice(0, str.length + str.length % 4); - // convert RFC 3548 to standard Base64 - str = str.replace(/-/g, '+').replace(/_/g, '/'); - // 80 column width - return str.replace(/(.{80})/g, '$1\n'); -}; + static re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/; + static unarmor(a) { + let m = Base64.re.exec(a); + if (m) { + if (m[1]) + a = m[1]; + else if (m[2]) + a = m[2]; + else if (m[3]) + a = m[3]; + else + throw 'RegExp out of sync'; + } + return Base64.decode(a); + }; -Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/; -Base64.unarmor = function (a) { - let m = Base64.re.exec(a); - if (m) { - if (m[1]) - a = m[1]; - else if (m[2]) - a = m[2]; - else if (m[3]) - a = m[3]; - else - throw 'RegExp out of sync'; - } - return Base64.decode(a); -}; +} return Base64; diff --git a/int10.js b/int10.js index c80cd41..ad1c5be 100644 --- a/int10.js +++ b/int10.js @@ -1,5 +1,5 @@ // Big integer base-10 printing library -// Copyright (c) 2008-2022 Lapo Luchini +// Copyright (c) 2008-2023 Lapo Luchini // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -21,92 +21,95 @@ let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 -/** - * Arbitrary length base-10 value. - * @param {number} value - Optional initial value (will be 0 otherwise). - */ -function Int10(value) { - this.buf = [+value || 0]; -} +class Int10 { + /** + * Arbitrary length base-10 value. + * @param {number} value - Optional initial value (will be 0 otherwise). + */ + constructor(value) { + this.buf = [+value || 0]; + } -/** - * Multiply value by m and add c. - * @param {number} m - multiplier, must be < =256 - * @param {number} c - value to add - */ -Int10.prototype.mulAdd = function (m, c) { - // assert(m <= 256) - let b = this.buf, - l = b.length, - i, t; - for (i = 0; i < l; ++i) { - t = b[i] * m + c; - if (t < max) - c = 0; - else { - c = 0|(t / max); - t -= c * max; + /** + * Multiply value by m and add c. + * @param {number} m - multiplier, must be < =256 + * @param {number} c - value to add + */ + mulAdd(m, c) { + // assert(m <= 256) + let b = this.buf, + l = b.length, + i, t; + for (i = 0; i < l; ++i) { + t = b[i] * m + c; + if (t < max) + c = 0; + else { + c = 0|(t / max); + t -= c * max; + } + b[i] = t; } - b[i] = t; - } - if (c > 0) - b[i] = c; -}; + if (c > 0) + b[i] = c; + }; -/** - * Subtract value. - * @param {number} c - value to subtract - */ -Int10.prototype.sub = function (c) { - let b = this.buf, - l = b.length, - i, t; - for (i = 0; i < l; ++i) { - t = b[i] - c; - if (t < 0) { - t += max; - c = 1; - } else - c = 0; - b[i] = t; - } - while (b[b.length - 1] === 0) - b.pop(); -}; + /** + * Subtract value. + * @param {number} c - value to subtract + */ + sub(c) { + let b = this.buf, + l = b.length, + i, t; + for (i = 0; i < l; ++i) { + t = b[i] - c; + if (t < 0) { + t += max; + c = 1; + } else + c = 0; + b[i] = t; + } + while (b[b.length - 1] === 0) + b.pop(); + }; + + /** + * Convert to decimal string representation. + * @param {*} base - optional value, only value accepted is 10 + */ + toString(base) { + if ((base || 10) != 10) + throw 'only base 10 is supported'; + let b = this.buf, + s = b[b.length - 1].toString(); + for (let i = b.length - 2; i >= 0; --i) + s += (max + b[i]).toString().substring(1); + return s; + }; -/** - * Convert to decimal string representation. - * @param {*} base - optional value, only value accepted is 10 - */ -Int10.prototype.toString = function (base) { - if ((base || 10) != 10) - throw 'only base 10 is supported'; - let b = this.buf, - s = b[b.length - 1].toString(); - for (let i = b.length - 2; i >= 0; --i) - s += (max + b[i]).toString().substring(1); - return s; -}; + /** + * Convert to Number value representation. + * Will probably overflow 2^53 and thus become approximate. + */ + valueOf() { + let b = this.buf, + v = 0; + for (let i = b.length - 1; i >= 0; --i) + v = v * max + b[i]; + return v; + }; -/** - * Convert to Number value representation. - * Will probably overflow 2^53 and thus become approximate. - */ -Int10.prototype.valueOf = function () { - let b = this.buf, - v = 0; - for (let i = b.length - 1; i >= 0; --i) - v = v * max + b[i]; - return v; -}; + /** + * Return value as a simple Number (if it is <= 10000000000000), or return this. + */ + simplify() { + let b = this.buf; + return (b.length == 1) ? b[0] : this; + }; -/** - * Return value as a simple Number (if it is <= 10000000000000), or return this. - */ -Int10.prototype.simplify = function () { - let b = this.buf; - return (b.length == 1) ? b[0] : this; -}; +} return Int10;