JavaScript generic ASN.1 parser (mirror)

Compare changes

Choose any two refs to compare.

+137 -67
+3 -1
index.css
··· 1 1 html { 2 2 --main-bg-color: #C0C0C0; 3 3 --main-text-color: #000000; 4 + --id-text-color: #7d5900; 4 5 --headline-text-color: #8be9fd; 5 6 --button-border-color: #767676; 6 7 --button-bg-color: #efefef; ··· 35 36 html[data-theme="dark"] { 36 37 --main-bg-color: #0d1116; 37 38 --main-text-color: #f8f8f2; 39 + --id-text-color: #9d7c2d; 38 40 --headline-text-color: #8be9fd; 39 41 --button-border-color: #505050; 40 42 --button-bg-color: #303030; ··· 186 188 color: var(--preview-border-color); 187 189 } 188 190 .name.id { 189 - color: var(--main-text-color); 191 + color: var(--id-text-color); 190 192 } 191 193 .value { 192 194 display: none;
+13 -7
int10.js
··· 13 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 15 16 - let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 16 + /** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */ 17 + const max = 10000000000000; 17 18 18 19 export class Int10 { 19 20 /** ··· 26 27 27 28 /** 28 29 * Multiply value by m and add c. 29 - * @param {number} m - multiplier, must be < =256 30 - * @param {number} c - value to add 30 + * @param {number} m - multiplier, must be 0<m<=256 31 + * @param {number} c - value to add, must be c>=0 31 32 */ 32 33 mulAdd(m, c) { 34 + // assert(m > 0) 33 35 // assert(m <= 256) 36 + // assert(c >= 0) 34 37 let b = this.buf, 35 38 l = b.length, 36 39 i, t; ··· 71 74 72 75 /** 73 76 * Convert to decimal string representation. 74 - * @param {*} base - optional value, only value accepted is 10 77 + * @param {number} [base=10] - optional value, only value accepted is 10 78 + * @returns {string} The decimal string representation. 75 79 */ 76 - toString(base) { 77 - if ((base || 10) != 10) 78 - throw 'only base 10 is supported'; 80 + toString(base = 10) { 81 + if (base != 10) 82 + throw new Error('only base 10 is supported'); 79 83 let b = this.buf, 80 84 s = b[b.length - 1].toString(); 81 85 for (let i = b.length - 2; i >= 0; --i) ··· 86 90 /** 87 91 * Convert to Number value representation. 88 92 * Will probably overflow 2^53 and thus become approximate. 93 + * @returns {number} The numeric value. 89 94 */ 90 95 valueOf() { 91 96 let b = this.buf, ··· 97 102 98 103 /** 99 104 * Return value as a simple Number (if it is <= 10000000000000), or return this. 105 + * @returns {number | Int10} The simplified value. 100 106 */ 101 107 simplify() { 102 108 let b = this.buf;
+121 -59
test.js
··· 3 3 import { ASN1, Stream } from './asn1.js'; 4 4 import { Hex } from './hex.js'; 5 5 import { Base64 } from './base64.js'; 6 + import { Int10 } from './int10.js'; 6 7 7 - const 8 - all = (process.argv[2] == 'all'); 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; 9 33 10 - const tests = [ 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 + }, [ 11 90 // RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html 12 91 ['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'], 13 92 ['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'], ··· 89 168 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 90 169 ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 91 170 ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 92 - ]; 171 + ])); 93 172 94 - const testsB64 = [ 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 + }, [ 95 185 'AA==', 96 186 'ABA=', 97 187 'ABCD', ··· 99 189 'ABCDEFE=', 100 190 'ABCDEFGH', 101 191 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR\nSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456w==', 102 - ]; 192 + ])); 103 193 104 - function check(result, expected, comment) { 105 - if (!result || result == expected) { 106 - if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 107 - return true; 108 - } else { 109 - console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 110 - console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 111 - console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 112 - return false; 113 - } 114 - } 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 + ])); 115 214 116 - let 117 - run = 0, 118 - expErr = 0, 119 - error = 0; 120 - for (let t of tests) { 121 - const input = t[0], 122 - expected = t[1], 123 - comment = t[2]; 124 - let result; 125 - try { 126 - let node = ASN1.decode(Hex.decode(input)); 127 - if (typeof expected == 'function') 128 - result = expected(node); 129 - else 130 - result = node.content(); 131 - //TODO: check structure, not only first level content 132 - } catch (e) { 133 - result = 'Exception:\n' + e; 134 - } 135 - if (expected instanceof RegExp) 136 - result = expected.test(result) ? null : 'does not match'; 137 - ++run; 138 - if (!check(result, expected, comment)) 139 - ++error; 140 - } 141 - for (let t of testsB64) { 142 - let bin = Base64.decode(t); 143 - let url = new Stream(bin, 0).b64Dump(0, bin.length); 144 - ++run; 145 - if (!check(url, t.replace(/\n/g, '').replace(/=*$/g, ''), 'Base64url: ' + bin.length + ' bytes')) 146 - ++error; 147 - let pretty = Base64.pretty(url); 148 - ++run; 149 - if (!check(pretty, t, 'Base64pretty: ' + bin.length + ' bytes')) 150 - ++error; 151 - let std = new Stream(bin, 0).b64Dump(0, bin.length, 'std'); 152 - ++run; 153 - if (!check(std, t.replace(/\n/g, ''), 'Base64: ' + bin.length + ' bytes')) 154 - ++error; 155 - } 156 - console.log(run + ' tested, ' + expErr + ' expected, ' + error + ' errors.'); 157 - process.exit(error ? 1 : 0); 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);