JavaScript generic ASN.1 parser (mirror)

Compare changes

Choose any two refs to compare.

+220 -70
+40
.vscode/launch.json
··· 7 { 8 "type": "node", 9 "request": "launch", 10 "name": "parseRFC", 11 "skipFiles": [ 12 "<node_internals>/**" ··· 15 "args": [ 16 "rfc/rfc4511.txt", 17 "rfcdef.json" 18 ] 19 }, 20 {
··· 7 { 8 "type": "node", 9 "request": "launch", 10 + "name": "dumpASN1", 11 + "skipFiles": [ 12 + "<node_internals>/**" 13 + ], 14 + "program": "${workspaceFolder}/dumpASN1.js", 15 + "args": [ 16 + "examples/ed25519.cer" 17 + ] 18 + }, 19 + { 20 + "type": "node", 21 + "request": "launch", 22 "name": "parseRFC", 23 "skipFiles": [ 24 "<node_internals>/**" ··· 27 "args": [ 28 "rfc/rfc4511.txt", 29 "rfcdef.json" 30 + ] 31 + }, 32 + { 33 + "type": "node", 34 + "request": "launch", 35 + "name": "dumpASN1 cert", 36 + "skipFiles": [ 37 + "<node_internals>/**" 38 + ], 39 + "program": "${workspaceFolder}/dumpASN1.js", 40 + "args": [ 41 + "data:base64,MIG9AgEBMA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUUjM0IFNhbXBsZXMxGzAZBgNVBAMTElRSMzQgU2FtcGxlIENBIEtESBcNMTAxMTAyMTczMzMwWhcNMTAxMjAyMTczMzMwWjBIMBYCBTQAAAAIFw0xMDExMDIxNzI4MTNaMBYCBTQAAAAKFw0xMDExMDIxNzMxNDZaMBYCBTQAAAALFw0xMDExMDIxNzMzMjVa", 42 + "1.3.6.1.5.5.7.0.18", 43 + "TBSCertList" 44 + ] 45 + }, 46 + { 47 + "type": "node", 48 + "request": "launch", 49 + "name": "dumpASN1 CMS", 50 + "skipFiles": [ 51 + "<node_internals>/**" 52 + ], 53 + "program": "${workspaceFolder}/dumpASN1.js", 54 + "args": [ 55 + "examples/cms-password.p7m", 56 + "1.2.840.113549.1.9.16.0.14", 57 + "ContentInfo" 58 ] 59 }, 60 {
+22 -16
asn1.js
··· 21 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)?)?$/, 22 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)?)?$/, 23 hexDigits = '0123456789ABCDEF', 24 - b64Safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 25 tableT61 = [ 26 ['', ''], 27 ['AEIOUaeiou', 'ร€รˆรŒร’ร™ร รจรฌรฒรน'], // Grave ··· 58 59 /** Class to manage a stream of bytes, with a zero-copy approach. 60 * It uses an existing array or binary string and advances a position index. */ 61 - class Stream { 62 63 /** 64 * @param {Stream|array|string} enc data (will not be copied) ··· 98 /** Hexadecimal dump of a specified region of the stream. 99 * @param {number} start starting position (included) 100 * @param {number} end ending position (excluded) 101 - * @param {string} type 'raw', 'byte' or 'dump' */ 102 hexDump(start, end, type = 'dump') { 103 let s = ''; 104 for (let i = start; i < end; ++i) { ··· 114 } 115 return s; 116 } 117 - /** Base-64 dump of a specified region of the stream. 118 * @param {number} start starting position (included) 119 - * @param {number} end ending position (excluded) */ 120 - b64Dump(start, end) { 121 let extra = (end - start) % 3, 122 s = '', 123 i, c; 124 for (i = start; i + 2 < end; i += 3) { 125 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); 126 - s += b64Safe.charAt(c >> 18 & 0x3F); 127 - s += b64Safe.charAt(c >> 12 & 0x3F); 128 - s += b64Safe.charAt(c >> 6 & 0x3F); 129 - s += b64Safe.charAt(c & 0x3F); 130 } 131 if (extra > 0) { 132 c = this.get(i) << 16; 133 if (extra > 1) c |= this.get(i + 1) << 8; 134 - s += b64Safe.charAt(c >> 18 & 0x3F); 135 - s += b64Safe.charAt(c >> 12 & 0x3F); 136 - if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F); 137 } 138 return s; 139 } ··· 557 toHexString(type = 'raw') { 558 return this.stream.hexDump(this.posStart(), this.posEnd(), type); 559 } 560 - /** Base64 dump of the node. */ 561 - toB64String() { 562 - return this.stream.b64Dump(this.posStart(), this.posEnd()); 563 } 564 static decodeLength(stream) { 565 let buf = stream.get(),
··· 21 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)?)?$/, 22 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)?)?$/, 23 hexDigits = '0123456789ABCDEF', 24 + b64Std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 25 + b64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', 26 tableT61 = [ 27 ['', ''], 28 ['AEIOUaeiou', 'ร€รˆรŒร’ร™ร รจรฌรฒรน'], // Grave ··· 59 60 /** Class to manage a stream of bytes, with a zero-copy approach. 61 * It uses an existing array or binary string and advances a position index. */ 62 + export class Stream { 63 64 /** 65 * @param {Stream|array|string} enc data (will not be copied) ··· 99 /** Hexadecimal dump of a specified region of the stream. 100 * @param {number} start starting position (included) 101 * @param {number} end ending position (excluded) 102 + * @param {string} type 'raw', 'byte' or 'dump' (default) */ 103 hexDump(start, end, type = 'dump') { 104 let s = ''; 105 for (let i = start; i < end; ++i) { ··· 115 } 116 return s; 117 } 118 + /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5). 119 * @param {number} start starting position (included) 120 + * @param {number} end ending position (excluded) 121 + * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */ 122 + b64Dump(start, end, type = 'url') { 123 + const b64 = type === 'url' ? b64URL : b64Std; 124 let extra = (end - start) % 3, 125 s = '', 126 i, c; 127 for (i = start; i + 2 < end; i += 3) { 128 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); 129 + s += b64.charAt(c >> 18 & 0x3F); 130 + s += b64.charAt(c >> 12 & 0x3F); 131 + s += b64.charAt(c >> 6 & 0x3F); 132 + s += b64.charAt(c & 0x3F); 133 } 134 if (extra > 0) { 135 c = this.get(i) << 16; 136 if (extra > 1) c |= this.get(i + 1) << 8; 137 + s += b64.charAt(c >> 18 & 0x3F); 138 + s += b64.charAt(c >> 12 & 0x3F); 139 + if (extra == 2) s += b64.charAt(c >> 6 & 0x3F); 140 + if (b64 === b64Std) s += '==='.slice(0, 3 - extra); 141 } 142 return s; 143 } ··· 561 toHexString(type = 'raw') { 562 return this.stream.hexDump(this.posStart(), this.posEnd(), type); 563 } 564 + /** Base64url dump of the node (according to RFC 4648 section 5). 565 + * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) 566 + */ 567 + toB64String(type = 'url') { 568 + return this.stream.b64Dump(this.posStart(), this.posEnd(), type); 569 } 570 static decodeLength(stream) { 571 let buf = stream.get(),
+6 -5
base64.js
··· 31 decoder[b64.charCodeAt(i)] = i; 32 for (i = 0; i < ignore.length; ++i) 33 decoder[ignore.charCodeAt(i)] = -1; 34 - // RFC 3548 URL & file safe encoding 35 decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; 36 decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; 37 } ··· 75 76 static pretty(str) { 77 // fix padding 78 - if (str.length % 4 > 0) 79 - str = (str + '===').slice(0, str.length + str.length % 4); 80 - // convert RFC 3548 to standard Base64 81 str = str.replace(/-/g, '+').replace(/_/g, '/'); 82 // 80 column width 83 - return str.replace(/(.{80})/g, '$1\n'); 84 } 85 86 static unarmor(a) {
··· 31 decoder[b64.charCodeAt(i)] = i; 32 for (i = 0; i < ignore.length; ++i) 33 decoder[ignore.charCodeAt(i)] = -1; 34 + // also support decoding Base64url (RFC 4648 section 5) 35 decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; 36 decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; 37 } ··· 75 76 static pretty(str) { 77 // fix padding 78 + let pad = 4 - str.length % 4; 79 + if (pad < 4) 80 + str += '==='.slice(0, pad); 81 + // convert Base64url (RFC 4648 section 5) to standard Base64 (RFC 4648 section 4) 82 str = str.replace(/-/g, '+').replace(/_/g, '/'); 83 // 80 column width 84 + return str.replace(/.{80}/g, '$&\n'); 85 } 86 87 static unarmor(a) {
+3 -1
index.css
··· 1 html { 2 --main-bg-color: #C0C0C0; 3 --main-text-color: #000000; 4 --headline-text-color: #8be9fd; 5 --button-border-color: #767676; 6 --button-bg-color: #efefef; ··· 35 html[data-theme="dark"] { 36 --main-bg-color: #0d1116; 37 --main-text-color: #f8f8f2; 38 --headline-text-color: #8be9fd; 39 --button-border-color: #505050; 40 --button-bg-color: #303030; ··· 186 color: var(--preview-border-color); 187 } 188 .name.id { 189 - color: var(--main-text-color); 190 } 191 .value { 192 display: none;
··· 1 html { 2 --main-bg-color: #C0C0C0; 3 --main-text-color: #000000; 4 + --id-text-color: #7d5900; 5 --headline-text-color: #8be9fd; 6 --button-border-color: #767676; 7 --button-bg-color: #efefef; ··· 36 html[data-theme="dark"] { 37 --main-bg-color: #0d1116; 38 --main-text-color: #f8f8f2; 39 + --id-text-color: #9d7c2d; 40 --headline-text-color: #8be9fd; 41 --button-border-color: #505050; 42 --button-bg-color: #303030; ··· 188 color: var(--preview-border-color); 189 } 190 .name.id { 191 + color: var(--id-text-color); 192 } 193 .value { 194 display: none;
+1 -1
index.html
··· 37 <div id="tree"></div> 38 </div> 39 <form> 40 - <textarea id="area" rows="8"></textarea> 41 <br> 42 <br> 43 <label title="can be slow with big files"><input type="checkbox" id="wantHex" checked="checked"> with hex dump</label>
··· 37 <div id="tree"></div> 38 </div> 39 <form> 40 + <textarea id="area" rows="8" placeholder="Paste hex or base64 or PEM encoded ASN.1 BER or DER structures here, or load a file."></textarea> 41 <br> 42 <br> 43 <label title="can be slow with big files"><input type="checkbox" id="wantHex" checked="checked"> with hex dump</label>
+13 -7
int10.js
··· 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16 - let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 17 18 export class Int10 { 19 /** ··· 26 27 /** 28 * Multiply value by m and add c. 29 - * @param {number} m - multiplier, must be < =256 30 - * @param {number} c - value to add 31 */ 32 mulAdd(m, c) { 33 // assert(m <= 256) 34 let b = this.buf, 35 l = b.length, 36 i, t; ··· 71 72 /** 73 * Convert to decimal string representation. 74 - * @param {*} base - optional value, only value accepted is 10 75 */ 76 - toString(base) { 77 - if ((base || 10) != 10) 78 - throw 'only base 10 is supported'; 79 let b = this.buf, 80 s = b[b.length - 1].toString(); 81 for (let i = b.length - 2; i >= 0; --i) ··· 86 /** 87 * Convert to Number value representation. 88 * Will probably overflow 2^53 and thus become approximate. 89 */ 90 valueOf() { 91 let b = this.buf, ··· 97 98 /** 99 * Return value as a simple Number (if it is <= 10000000000000), or return this. 100 */ 101 simplify() { 102 let b = this.buf;
··· 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16 + /** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */ 17 + const max = 10000000000000; 18 19 export class Int10 { 20 /** ··· 27 28 /** 29 * Multiply value by m and add c. 30 + * @param {number} m - multiplier, must be 0<m<=256 31 + * @param {number} c - value to add, must be c>=0 32 */ 33 mulAdd(m, c) { 34 + // assert(m > 0) 35 // assert(m <= 256) 36 + // assert(c >= 0) 37 let b = this.buf, 38 l = b.length, 39 i, t; ··· 74 75 /** 76 * Convert to decimal string representation. 77 + * @param {number} [base=10] - optional value, only value accepted is 10 78 + * @returns {string} The decimal string representation. 79 */ 80 + toString(base = 10) { 81 + if (base != 10) 82 + throw new Error('only base 10 is supported'); 83 let b = this.buf, 84 s = b[b.length - 1].toString(); 85 for (let i = b.length - 2; i >= 0; --i) ··· 90 /** 91 * Convert to Number value representation. 92 * Will probably overflow 2^53 and thus become approximate. 93 + * @returns {number} The numeric value. 94 */ 95 valueOf() { 96 let b = this.buf, ··· 102 103 /** 104 * Return value as a simple Number (if it is <= 10000000000000), or return this. 105 + * @returns {number | Int10} The simplified value. 106 */ 107 simplify() { 108 let b = this.buf;
+2 -2
package.json
··· 1 { 2 "name": "@lapo/asn1js", 3 - "version": "2.0.5", 4 "description": "Generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.", 5 "type": "module", 6 "main": "asn1.js", ··· 18 "lint": "npx eslint asn1.js base64.js hex.js int10.js dom.js defs.js oids.js rfcdef.js tags.js context.js index.js parseRFC.js dumpASN1.js test.js testDefs.js vite.config.js theme.js", 19 "lint-action": "npx @action-validator/cli .github/workflows/node.js.yml", 20 "build": "vite build", 21 - "serve": "echo 'Connect to http://localhost:3000/' ; npx statik --port 3000 .", 22 "test": "node test", 23 "testdefs": "node testDefs" 24 },
··· 1 { 2 "name": "@lapo/asn1js", 3 + "version": "2.0.6", 4 "description": "Generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.", 5 "type": "module", 6 "main": "asn1.js", ··· 18 "lint": "npx eslint asn1.js base64.js hex.js int10.js dom.js defs.js oids.js rfcdef.js tags.js context.js index.js parseRFC.js dumpASN1.js test.js testDefs.js vite.config.js theme.js", 19 "lint-action": "npx @action-validator/cli .github/workflows/node.js.yml", 20 "build": "vite build", 21 + "serve": "npx -p local-web-server ws", 22 "test": "node test", 23 "testdefs": "node testDefs" 24 },
+1
tags.js
··· 1 export const tags = { 2 "2.0.4": "2024-05-08", 3 "2.0.3": "2024-05-06", 4 "2.0.2": "2024-04-20",
··· 1 export const tags = { 2 + "2.0.5": "2025-04-12", 3 "2.0.4": "2024-05-08", 4 "2.0.3": "2024-05-06", 5 "2.0.2": "2024-04-20",
+132 -38
test.js
··· 1 #!/usr/bin/env node 2 3 - import { ASN1 } from './asn1.js'; 4 import { Hex } from './hex.js'; 5 6 - const 7 - all = (process.argv[2] == 'all'); 8 9 - const tests = [ 10 // RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html 11 ['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'], 12 ['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'], ··· 88 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 89 ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 90 ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 91 - ]; 92 93 - let 94 - run = 0, 95 - expErr = 0, 96 - error = 0; 97 - tests.forEach(function (t) { 98 - const input = t[0], 99 - expected = t[1], 100 - comment = t[2]; 101 - let result; 102 - try { 103 - let node = ASN1.decode(Hex.decode(input)); 104 - if (typeof expected == 'function') 105 - result = expected(node); 106 - else 107 - result = node.content(); 108 - //TODO: check structure, not only first level content 109 - } catch (e) { 110 - result = 'Exception:\n' + e; 111 - } 112 - if (expected instanceof RegExp) 113 - result = expected.test(result) ? null : 'does not match'; 114 - ++run; 115 - if (!result || result == expected) { 116 - if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 117 - } else { 118 - ++error; 119 - console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 120 - console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 121 - console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 122 - } 123 - }); 124 - console.log(run + ' tested, ' + expErr + ' expected, ' + error + ' errors.'); 125 - process.exit(error ? 1 : 0);
··· 1 #!/usr/bin/env node 2 3 + import { ASN1, Stream } from './asn1.js'; 4 import { Hex } from './hex.js'; 5 + import { Base64 } from './base64.js'; 6 + import { Int10 } from './int10.js'; 7 + 8 + const all = (process.argv[2] == 'all'); 9 10 + /** @type {Array<Tests>} */ 11 + const tests = []; 12 + 13 + const stats = { 14 + run: 0, 15 + error: 0, 16 + }; 17 + 18 + /** 19 + * A class for managing and executing tests. 20 + */ 21 + class Tests { 22 + /** 23 + * An array to store test data. 24 + * @type {Array<unknown>} 25 + */ 26 + data; 27 + 28 + /** 29 + * Checks a row of test data. 30 + * @param {Function} t - How to test a row of data. 31 + */ 32 + checkRow; 33 + 34 + /** 35 + * Constructs a new Tests instance. 36 + * @param {Function} checkRow - A function to check each row of data. 37 + * @param {Array<unknown>} data - The test data to be processed. 38 + */ 39 + constructor(checkRow, data) { 40 + this.checkRow = checkRow; 41 + this.data = data; 42 + } 43 + 44 + /** 45 + * Executes the tests and checks their results for all rows. 46 + */ 47 + checkAll() { 48 + for (const t of this.data) 49 + this.checkRow(t); 50 + } 51 52 + /** 53 + * Prints the result of a test, indicating if it passed or failed. 54 + * @param {unknown} result The actual result of the test. 55 + * @param {unknown} expected The expected result of the test. 56 + * @param {string} comment A comment describing the test. 57 + */ 58 + checkResult(result, expected, comment) { 59 + ++stats.run; 60 + if (!result || result == expected) { 61 + if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 62 + } else { 63 + ++stats.error; 64 + console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 65 + console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 66 + console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 67 + } 68 + } 69 + } 70 + 71 + tests.push(new Tests(function (t) { 72 + const input = t[0], 73 + expected = t[1], 74 + comment = t[2]; 75 + let result; 76 + try { 77 + let node = ASN1.decode(Hex.decode(input)); 78 + if (typeof expected == 'function') 79 + result = expected(node); 80 + else 81 + result = node.content(); 82 + //TODO: check structure, not only first level content 83 + } catch (e) { 84 + result = 'Exception:\n' + e; 85 + } 86 + if (expected instanceof RegExp) 87 + result = expected.test(result) ? null : 'does not match'; 88 + this.checkResult(result, expected, comment); 89 + }, [ 90 // RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html 91 ['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'], 92 ['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'], ··· 168 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 169 ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 170 ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 171 + ])); 172 173 + tests.push(new Tests(function (t) { 174 + let bin = Base64.decode(t); 175 + let url = new Stream(bin, 0).b64Dump(0, bin.length); 176 + // check base64url encoding 177 + this.checkResult(url, t.replace(/\n/g, '').replace(/=*$/g, ''), 'Base64url: ' + bin.length + ' bytes'); 178 + // check conversion from base64url to base64 179 + let pretty = Base64.pretty(url); 180 + this.checkResult(pretty, t, 'Base64pretty: ' + bin.length + ' bytes'); 181 + let std = new Stream(bin, 0).b64Dump(0, bin.length, 'std'); 182 + // check direct base64 encoding 183 + this.checkResult(std, t.replace(/\n/g, ''), 'Base64: ' + bin.length + ' bytes'); 184 + }, [ 185 + 'AA==', 186 + 'ABA=', 187 + 'ABCD', 188 + 'ABCDEA==', 189 + 'ABCDEFE=', 190 + 'ABCDEFGH', 191 + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR\nSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456w==', 192 + ])); 193 + 194 + tests.push(new Tests(function (t) { 195 + this.row = (0|this.row) + 1; 196 + this.num = this.num || new Int10(); 197 + this.num.mulAdd(t[0], t[1]); 198 + this.checkResult(this.num.toString(), t[2], 'Int10 row ' + this.row); 199 + }, [ 200 + [0, 1000000000, '1000000000'], 201 + [256, 23, '256000000023'], 202 + [256, 23, '65536000005911'], 203 + [256, 23, '16777216001513239'], 204 + [256, 23, '4294967296387389207'], 205 + [256, 23, '1099511627875171637015'], 206 + [256, 23, '281474976736043939075863'], 207 + [253, 1, '71213169114219116586193340'], 208 + [253, 1, '18016931785897436496306915021'], 209 + [253, 1, '4558283741832051433565649500314'], 210 + [253, 1, '1153245786683509012692109323579443'], 211 + [253, 1, '291771184030927780211103658865599080'], 212 + [1, 0, '291771184030927780211103658865599080'], 213 + ])); 214 + 215 + for (const t of tests) 216 + t.checkAll(); 217 + 218 + console.log(stats.run + ' tested, ' + stats.error + ' errors.'); 219 + process.exit(stats.error ? 1 : 0);