├── .gitignore ├── .gitattributes ├── README.md ├── doc └── index.html ├── test ├── example.js ├── assertions.js ├── exp3.js ├── sequence.js ├── clone.js ├── inter.js ├── keyconstraints.js ├── hex.js ├── nondeterministic.js ├── out-of-range.js ├── base64.js ├── blah.js ├── exp2.js ├── utf8.js ├── experiments.js ├── float32.js ├── test.js ├── tags.js ├── float.js ├── buggor.js └── save-cbor-js-api.js ├── LICENSE └── src └── cbor-js-api.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | cbor-js-api.sln 3 | webverify/ 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable LF normalization for all files 2 | * -text -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo has been replaced by https://github.com/cyberphone/CBOR.js 2 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | To be written... 4 | 5 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | 4 | let cbor = CBOR.Map() 5 | .set(CBOR.Int(1), CBOR.Float(45.7)) 6 | .set(CBOR.Int(2), CBOR.String("Hi there!")).encode(); 7 | 8 | console.log(CBOR.toHex(cbor)); -------------------------------------------------------------------------------- /test/assertions.js: -------------------------------------------------------------------------------- 1 | function assertTrue(text, bool) { 2 | if (!bool) throw Error("Assertion: " + text); 3 | } 4 | 5 | function assertFalse(text, bool) { 6 | if (bool) throw Error("Assertion: " + text); 7 | } 8 | 9 | exports.assertFalse = assertFalse; 10 | exports.assertTrue = assertTrue; 11 | -------------------------------------------------------------------------------- /test/exp3.js: -------------------------------------------------------------------------------- 1 | const regex = /\-?\d+/g; 2 | function oneTurn(number) { 3 | if (number.indexOf('.') < 0) { 4 | let ret = number.match(regex); 5 | if (ret) { 6 | number = ret[0] + '.0' + number.substring(ret[0].length); 7 | } 8 | } 9 | console.log(number); 10 | } 11 | 12 | oneTurn("NaN"); 13 | oneTurn("1"); 14 | oneTurn("-0"); 15 | oneTurn("0.3"); 16 | oneTurn("1e+77"); 17 | oneTurn("950"); -------------------------------------------------------------------------------- /test/sequence.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | let cbor = new Uint8Array([0x05, 0xa1, 0x05, 0x42, 0x6a, 0x6a]) 4 | try { 5 | console.log(CBOR.decode(cbor).toString()); 6 | throw Error("API error"); 7 | } catch (error) { 8 | if (!error.toString().includes('Unexpected')) console.log(error); 9 | } 10 | let decoder = CBOR.initExtended(cbor, true, false, false); 11 | let object; 12 | while (object = CBOR.decodeExtended(decoder)) { 13 | console.log("SO=" + object.toString()); 14 | } -------------------------------------------------------------------------------- /test/clone.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | const assertTrue = require('./assertions.js').assertTrue; 4 | const assertFalse = require('./assertions.js').assertFalse; 5 | 6 | let object = CBOR.Map() 7 | .set(CBOR.Int(2), CBOR.Array() 8 | .add(CBOR.Bool(false))); 9 | assertTrue("clone+equals", object.equals(object.clone())); 10 | let copy = object.clone().set(CBOR.Int(1), CBOR.String("Hi")); 11 | assertFalse("copy+equals+clone", copy.equals(object)); 12 | -------------------------------------------------------------------------------- /test/inter.js: -------------------------------------------------------------------------------- 1 | const CBOR = require('../src/cbor-js-api.js'); 2 | const prompt = require('prompt-sync')({sigint: true}); 3 | 4 | 'use strict'; 5 | 6 | while (true) { 7 | let text = prompt('Input BigInt (n or 0xn) : ').replace(/\ /g, ''); 8 | let radix = text.startsWith('0x') ? 16 : 10; 9 | let encodedCbor = CBOR.BigInt(BigInt(text)).encode(); 10 | console.log("Encoded: " + CBOR.toHex(encodedCbor)); 11 | let decodedCbor = CBOR.decode(encodedCbor); 12 | console.log("Decoded: " + 13 | (radix == 10 ? '' : '0x') + 14 | decodedCbor.getBigInt().toString(radix) + 15 | " type=CBOR." + 16 | decodedCbor.constructor.name); 17 | try { 18 | console.log("getInt(): " + 19 | (radix == 10 ? '' : '0x') + 20 | decodedCbor.getInt().toString(radix)); 21 | } catch (error) { 22 | console.log(error.toString()); 23 | } 24 | } -------------------------------------------------------------------------------- /test/keyconstraints.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | 'use strict'; 4 | 5 | oneTurn = function(cbor, ok) { 6 | try { 7 | CBOR.decode(cbor.encode()); 8 | } catch (error) { 9 | console.log("Err=" + error.toString()); 10 | } 11 | try { 12 | let decoder = CBOR.initExtended(cbor.encode(), false, false, true); 13 | let object = CBOR.decodeExtended(decoder); 14 | if (!ok) throw Error("Should not:"); 15 | } catch (error) { 16 | if (ok || !error.toString().includes('Constrained')) 17 | console.log("Err=" + error + " ok=" + ok); 18 | } 19 | } 20 | 21 | oneTurn(CBOR.Map().set(CBOR.Bool(true), CBOR.String("1")), false); 22 | oneTurn(CBOR.Map() 23 | .set(CBOR.Int(0), CBOR.String("0")) 24 | .set(CBOR.Int(1), CBOR.String("1")), true); 25 | oneTurn(CBOR.Map() 26 | .set(CBOR.String("mix"), CBOR.String("0")) 27 | .set(CBOR.Int(1), CBOR.String("1")), false); -------------------------------------------------------------------------------- /test/hex.js: -------------------------------------------------------------------------------- 1 | // Test program for the hex converters 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | const assertTrue = require('./assertions.js').assertTrue; 4 | const assertFalse = require('./assertions.js').assertFalse; 5 | 6 | const hex = '0123456789abcdefABCDEF'; 7 | 8 | let bin = CBOR.fromHex(hex); 9 | let cnv = CBOR.toHex(bin); 10 | assertFalse("hex", CBOR.compareArrays(bin, CBOR.fromHex(cnv))); 11 | let ref = new Uint8Array([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xAB, 0xCD, 0xEF]); 12 | assertFalse("bin", CBOR.compareArrays(bin, ref)); 13 | try { 14 | CBOR.fromHex("AAA"); 15 | throw Error("should not"); 16 | } catch (error) { 17 | if (!error.toString().includes("Unev")) { 18 | console.log(error); 19 | } 20 | } 21 | 22 | try { 23 | CBOR.fromHex("Ag"); 24 | throw Error("should not"); 25 | } catch (error) { 26 | if (!error.toString().includes("Bad hex")) { 27 | console.log(error); 28 | } 29 | } -------------------------------------------------------------------------------- /test/nondeterministic.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | 4 | oneTurn = function(hex, dn) { 5 | try { 6 | CBOR.decode(CBOR.fromHex(hex)); 7 | throw Error("Should not fail on: " + dn); 8 | } catch (error) { 9 | if (!error.toString().includes("Non-d")) { 10 | throw error; 11 | } 12 | } 13 | let decoder = CBOR.initExtended(CBOR.fromHex(hex), false, true, false); 14 | let object = CBOR.decodeExtended(decoder); 15 | if (object.toString() != dn.toString() || !object.equals(CBOR.decode(object.encode()))) { 16 | throw Error("non match:" + dn); 17 | } 18 | console.log(hex); 19 | } 20 | 21 | oneTurn('1900ff', '255'); 22 | oneTurn('1817', '23'); 23 | oneTurn('A2026374776F01636F6E65', '{\n 1: "one",\n 2: "two"\n}'); 24 | oneTurn('FB7FF8000000000000', 'NaN'); 25 | oneTurn('FB3ff0000000000000', '1.0'); 26 | oneTurn('f97e01', 'NaN'); 27 | oneTurn('c240', '0'); 28 | 29 | // This one is actually deterministic... 30 | oneTurn('fa7f7fffff', '3.4028234663852886e+38'); 31 | 32 | -------------------------------------------------------------------------------- /test/out-of-range.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | const assertTrue = require('./assertions.js').assertTrue; 4 | const assertFalse = require('./assertions.js').assertFalse; 5 | const TOO_BIG = Number.MAX_SAFE_INTEGER + 1; 6 | const IN_RANGE = Number.MAX_SAFE_INTEGER; 7 | 8 | try { 9 | CBOR.Int(TOO_BIG); 10 | throw Error('Should not'); 11 | } catch (error) { 12 | if (error.toString().includes('Should not')) { 13 | console.log(error); 14 | } 15 | } 16 | let cbor = CBOR.BigInt(BigInt(TOO_BIG)).encode(); 17 | try { 18 | CBOR.decode(cbor).getInt(); 19 | throw Error('Should not'); 20 | } catch (error) { 21 | if (error.toString().includes('Should not')) { 22 | console.log(error); 23 | } 24 | } 25 | assertTrue("big", BigInt(TOO_BIG) == CBOR.decode(cbor).getBigInt()); 26 | 27 | cbor = CBOR.Int(IN_RANGE).encode(); 28 | assertTrue("R0", CBOR.decode(cbor).getInt() == IN_RANGE); 29 | cbor = CBOR.Int(-IN_RANGE).encode(); 30 | assertTrue("R0", CBOR.decode(cbor).getInt() == -IN_RANGE); 31 | 32 | -------------------------------------------------------------------------------- /test/base64.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | const assertTrue = require('./assertions.js').assertTrue; 4 | const assertFalse = require('./assertions.js').assertFalse; 5 | 6 | let bin = new Uint8Array(256); 7 | for (let i = 0; i < bin.length; i++) { 8 | bin[i] = i; 9 | } 10 | let b64 = CBOR.toBase64Url(bin); 11 | console.log(btoa(String.fromCharCode.apply(null, new Uint8Array(bin))) 12 | .replace(/\+/g, '-').replace(/\//g, '_')); 13 | CBOR.fromBase64Url("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w="); 14 | assertFalse("cmp1", CBOR.compareArrays(bin, CBOR.fromBase64Url(b64))); 15 | assertFalse("cmp2", CBOR.compareArrays(CBOR.fromBase64Url('oQVkZGF0YQ'), 16 | CBOR.fromHex('a1056464617461'))); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anders Rundgren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/blah.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | let map = CBOR.Map() 4 | .set(CBOR.Int(3), CBOR.String("three")) 5 | .set(CBOR.Int(4), CBOR.String("four")); 6 | map.getKeys().forEach(element => console.log(element.toString())); 7 | console.log(map.get(CBOR.Int(4)).toString()); 8 | console.log("size=" + map.size()); 9 | console.log(map.remove(CBOR.Int(4)).toString()); 10 | console.log(map.containsKey(CBOR.Int(3))); 11 | console.log(map.containsKey(CBOR.Int(4))); 12 | console.log(map.getConditionally(CBOR.Int(3), CBOR.String("k3")).getString()); 13 | console.log(map.getConditionally(CBOR.Int(4), CBOR.String("k4")).getString()); 14 | console.log(map.toString()); 15 | let array = CBOR.Array().add(CBOR.Int(3)).add(CBOR.String("stringo")); 16 | console.log(array.get(1).toString()); 17 | console.log(array.toArray().length); 18 | console.log(map.get(CBOR.Int(3)).equals(CBOR.Int(5))); 19 | console.log(map.get(CBOR.Int(3)).equals(CBOR.String("three"))); 20 | console.log(map.get(CBOR.Int(3)).equals(null)); 21 | console.log(map.get(CBOR.Int(3)).equals("three")); 22 | console.log("size=" + map.size()); -------------------------------------------------------------------------------- /test/exp2.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | 'use strict'; 4 | 5 | function print(array) { 6 | let f64bytes = new Uint8Array(array); 7 | const f64buffer = new ArrayBuffer(8); 8 | new Uint8Array(f64buffer).set(f64bytes); 9 | console.log(new DataView(f64buffer).getFloat64(0, false)); 10 | } 11 | 12 | function process(f64Number) { 13 | console.log("\nINIT=" + f64Number); 14 | let cbor = CBOR.Float(f64Number).encode(); 15 | let f64 = CBOR.decode(cbor); 16 | console.log('Value=' + f64.toString()); 17 | } 18 | 19 | print([0x47, 0xef, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x01]); 20 | print([0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 21 | print([0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 22 | print([0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 23 | /* 24 | process(3.402823466385289e+38); 25 | process(0.0); 26 | process(Number.NaN); 27 | process(Number.NEGATIVE_INFINITY); 28 | process(Number.POSITIVE_INFINITY); 29 | */ 30 | process(0.5); 31 | process(-5.9604644775390625e-8); 32 | process(6.103515625e-5); 33 | process(10.559998512268066); 34 | process(5.960465188081798e-8); 35 | process(10.559998512268068); 36 | process(3.4028234663852886e+38); 37 | process(5.0e-324); 38 | -------------------------------------------------------------------------------- /test/utf8.js: -------------------------------------------------------------------------------- 1 | // Test program for floating point "edge cases" 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | const assertTrue = require('./assertions.js').assertTrue; 4 | const assertFalse = require('./assertions.js').assertFalse; 5 | 6 | 7 | function utf8EncoderTest(string, ok) { 8 | try { 9 | let cbor = CBOR.String(string).encode(); 10 | console.log("S=" + string); 11 | /* 12 | encodedString = CBORDiagnosticNotation.decode( 13 | "\"" + string + "\"").getString(); 14 | assertTrue("OK", ok); 15 | assertTrue("Conv", string.equals(encodedString)); 16 | byte[] encodedBytes = CBORDiagnosticNotation.decode( 17 | "'" + string + "'").getBytes(); 18 | assertTrue("OK", ok); 19 | assertTrue("Conv2", Arrays.equals(encodedBytes, string.getBytes("utf-8"))); 20 | */ 21 | } catch (error) { 22 | assertFalse("No good", ok); 23 | } 24 | 25 | } 26 | 27 | function utf8DecoderTest(hex, ok) { 28 | let cbor = CBOR.fromHex(hex); 29 | let roundTrip; 30 | try { 31 | roundTrip = CBOR.decode(cbor).encode(); 32 | } catch (error) { 33 | assertFalse("No good", ok); 34 | return; 35 | } 36 | assertTrue("OK", ok); 37 | assertFalse("Conv", CBOR.compareArrays(cbor, roundTrip)); 38 | } 39 | 40 | utf8DecoderTest("62c328", false); 41 | utf8DecoderTest("64f0288cbc", false); 42 | utf8DecoderTest("64f0908cbc", true); 43 | utf8EncoderTest("Hi", true) 44 | utf8EncoderTest("\uD83D", false); 45 | utf8EncoderTest("\uD83D\uDE2D", true); 46 | -------------------------------------------------------------------------------- /test/experiments.js: -------------------------------------------------------------------------------- 1 | // Testing alterntives 2 | 3 | 'use strict'; 4 | 5 | 6 | class CBOR { 7 | 8 | static CBORRoot = class { 9 | yy = function() { 10 | return this.getNumber(); 11 | } 12 | } 13 | 14 | static _Array = class extends CBOR.CBORRoot { 15 | static legs = 5; 16 | constructor() { 17 | super(); 18 | console.log('woof'); 19 | } 20 | 21 | getNumber = function() {return 6} 22 | } 23 | // progmatic use of `new` via .construct 24 | // preload the first argument with the class we want to call; 25 | // proxy the actual Reflect.construct method but point all gets and sets to the static Class constructor, in english: makes static available NOTE this does not mess with Reflect.construct 26 | static Int = new Proxy( 27 | Reflect.construct.bind(null, CBOR._Array), 28 | { 29 | get(tar, prop, val) { 30 | // access static 31 | return Reflect.get(CBOR._Array, prop, val); 32 | }, 33 | set(tar, prop, val) { 34 | // access static 35 | return Reflect.set(CBOR._Array, prop, val); 36 | }, 37 | apply(target, thisArg, argumentsList) { 38 | // make the constructor work 39 | return target({...argumentsList, length: argumentsList.length}); 40 | } 41 | } 42 | ); 43 | 44 | static hh= function() { 45 | return CBOR.Int().getNumber(); 46 | } 47 | 48 | } 49 | CBOR.Int().yy(); // calls constructor 50 | CBOR.Int.legs; // 5 51 | console.log(CBOR.hh()); 52 | console.log(CBOR.Int().yy()); 53 | -------------------------------------------------------------------------------- /test/float32.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | 4 | float32 = 0; 5 | float16 = 0; 6 | runs = 0; 7 | 8 | function convert(i) { 9 | try { 10 | let f32bytes = new Uint8Array(4); 11 | for (let q = 3; q >= 0; q--) { 12 | f32bytes[q] = i; 13 | i = (i / 256).toFixed(); 14 | } 15 | const f32buffer = new ArrayBuffer(4); 16 | new Uint8Array(f32buffer).set(f32bytes); 17 | let d = new DataView(f32buffer).getFloat32(0, false); 18 | let cbor = CBOR.Float(d).encode(); 19 | switch (cbor.length) { 20 | case 3: 21 | float16++; 22 | break; 23 | case 5: 24 | float32++; 25 | break; 26 | default: 27 | throw Error("BUG"); 28 | } 29 | let v = CBOR.decode(cbor).getFloat(); 30 | let status = false; 31 | if (Number.isNaN(d)) { 32 | status = !Number.isNaN(v); 33 | } else if (!Number.isFinite(d)) { 34 | status = Number.isFinite(v); 35 | } else if (Math.abs(d) == 0) { 36 | status = Object.is(d,-0) != Object.is(v,-0); 37 | } else { 38 | status = d != v; 39 | } 40 | if (status) { 41 | throw Error("Fail" + v + " " + d); 42 | } 43 | if ((++runs % 1000000) == 0) { 44 | console.log(" 16=" + float16 + " 32=" + float32); 45 | } 46 | } catch (error) { 47 | console.log("**********=" + i + " e=" + error); 48 | } 49 | 50 | } 51 | 52 | let f = 0; 53 | while (f < 0x800000) { 54 | let e = 0; 55 | while (e < 0x100) { 56 | convert((e * 0x800000) + f); 57 | e++; 58 | } 59 | f++; 60 | } 61 | System.out.println("Runs=" + Long.toString(runs)); 62 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | //console.log (CBOR.hex(CBOR.Array().add(CBOR.String("hi")).encode())); 4 | console.log (CBOR.Float(4.4).toString()); 5 | console.log (CBOR.toHex(CBOR.BigInt(-0x800000001n).encode())); 6 | console.log (CBOR.toHex(CBOR.Float(1.401298464324817e-45).encode())); 7 | console.log (CBOR.toHex(CBOR.Float(3.5).encode())); 8 | console.log (CBOR.toHex(CBOR.Float(-0.0).encode())); 9 | console.log (CBOR.toHex(CBOR.Float(-5.9604644775390625e-8).encode())); 10 | console.log (CBOR.toHex(CBOR.Float(-9.5367431640625e-7).encode())); 11 | console.log (CBOR.toHex(CBOR.Float(65504.0).encode())); 12 | // console.log (CBOR.Map().toString()); 13 | // console.log (CBOR.Map().set(CBOR.Int(4), CBOR.String("Yeah")).toString()); 14 | let cbor = CBOR.Map() 15 | .set(CBOR.Int(5), 16 | CBOR.Map() 17 | .set(CBOR.Int(8), CBOR.String("Ye\n\u0001ah€")) 18 | .set(CBOR.Int(9), 19 | CBOR.Array() 20 | .add(CBOR.String("Bytes!")) 21 | .add(CBOR.Bytes(new Uint8Array([1,2,3,4,5]))) 22 | .add(CBOR.Bool(true)) 23 | .add(CBOR.Bool(false)) 24 | .add(CBOR.Null()))) 25 | .set(CBOR.Int(4), CBOR.String("Sure")) 26 | .set(CBOR.Int(2), CBOR.Float(-9.5367431640625e-7)) 27 | .set(CBOR.Int(6), CBOR.BigInt(123456789123456789123456789n)) 28 | .set(CBOR.Int(1), CBOR.Tag(500, CBOR.Array().add(CBOR.Int(45)))); 29 | console.log(cbor.toString()); 30 | console.log(CBOR.toHex(cbor.encode())); 31 | -------------------------------------------------------------------------------- /test/tags.js: -------------------------------------------------------------------------------- 1 | // JavaScript source code 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | const assertTrue = require('./assertions.js').assertTrue; 4 | const assertFalse = require('./assertions.js').assertFalse; 5 | const TOO_BIG = Number.MAX_SAFE_INTEGER + 1; 6 | const IN_RANGE = Number.MAX_SAFE_INTEGER; 7 | 8 | cbor = CBOR.Tag(IN_RANGE, CBOR.Map()).encode(); 9 | assertTrue("t1", CBOR.decode(cbor).getTagNumber()== BigInt(IN_RANGE)); 10 | cbor = CBOR.Tag(BigInt(TOO_BIG), CBOR.Map()).encode(); 11 | assertTrue("t2", CBOR.decode(cbor).getTagNumber()== BigInt(TOO_BIG)); 12 | let object = CBOR.Array().add(CBOR.String("https://example.com/myobject")).add(CBOR.Int(6)); 13 | cbor = CBOR.Tag(CBOR.Tag.RESERVED_TAG_COTX, object).encode(); 14 | let tag = CBOR.decode(cbor); 15 | assertTrue("t3", tag.getTagNumber()== CBOR.Tag.RESERVED_TAG_COTX); 16 | assertTrue("t3.1", object.equals(tag.getTagObject())); 17 | tag = CBOR.decode(cbor).getTag(); // Redundant in JavaScript 18 | assertTrue("t3.2", object.equals(tag.getTagObject())); 19 | cbor = CBOR.Tag(0xf0123456789abcden, object).encode(); 20 | assertTrue("t14", CBOR.decode(cbor).getTagNumber()== 0xf0123456789abcden); 21 | assertTrue("t5", CBOR.toHex(cbor) == 22 | "dbf0123456789abcde82781c68747470733a2f2f6578616d706c652e636f6d2f6d796f626a65637406"); 23 | try { 24 | CBOR.Tag(-1, CBOR.String("minus")); 25 | throw Error("Should not"); 26 | } catch (error) { 27 | if (!error.toString().includes("out of range")) { 28 | console.log(error); 29 | } 30 | } 31 | try { 32 | CBOR.Tag(0x10000000000000000n, CBOR.String("minus")); 33 | throw Error("Should not"); 34 | } catch (error) { 35 | if (!error.toString().includes("out of range")) { 36 | console.log(error); 37 | } 38 | } 39 | 40 | try { 41 | let tag = CBOR.Int(5).getTag(); 42 | throw Error("Should not"); 43 | } catch (error) { 44 | if (!error.toString().includes("CBOR.Int")) { 45 | console.log(error); 46 | } 47 | } -------------------------------------------------------------------------------- /test/float.js: -------------------------------------------------------------------------------- 1 | // Test program for floating point "edge cases" 2 | const CBOR = require('../src/cbor-js-api.js'); 3 | 4 | errorCount = 0; 5 | 6 | function oneTurn(value, expected) { 7 | let text = value.toString(); 8 | while (text.length < 25) { 9 | text += ' '; 10 | } 11 | let cbor = CBOR.Float(value).encode(); 12 | let got = CBOR.toHex(cbor); 13 | if (got != expected) { 14 | errorCount++; 15 | got = '***=' + got; 16 | } else { 17 | got = ''; 18 | } 19 | if (CBOR.decode(cbor).getFloat() != value) { 20 | console.log("Failed decoding: " + value); 21 | errorCount++; 22 | } 23 | while (expected.length < 20) { 24 | expected += ' '; 25 | } 26 | console.log(text + expected + got); 27 | } 28 | 29 | oneTurn(6.10649585723877e-5, 'fa38801000'); 30 | oneTurn(10.559998512268066, 'fa4128f5c1'); 31 | oneTurn(65472.0, 'f97bfe'); 32 | oneTurn(65472.00390625, 'fa477fc001'); 33 | oneTurn(65503.0, 'fa477fdf00'); 34 | oneTurn(65504.0, 'f97bff'); 35 | oneTurn(65504.00390625, 'fa477fe001'); 36 | oneTurn(65504.5, 'fa477fe080'); 37 | oneTurn(65505.0, 'fa477fe100'); 38 | oneTurn(131008.0, 'fa47ffe000'); 39 | oneTurn(-5.9604644775390625e-8, 'f98001'); 40 | oneTurn(-5.960465188081798e-8, 'fab3800001'); 41 | oneTurn(-5.960465188081798e-8, 'fab3800001'); 42 | oneTurn(-5.963374860584736e-8, 'fab3801000'); 43 | oneTurn(-5.966285243630409e-8, 'fab3802000'); 44 | oneTurn(-8.940696716308594e-8, 'fab3c00000'); 45 | oneTurn(-0.00006097555160522461, 'f983ff'); 46 | oneTurn(-0.00006097555160522469, 'fbbf0ff8000000000c'); 47 | oneTurn(-0.000060975551605224615, 'fbbf0ff80000000001'); 48 | oneTurn(-0.0000609755516052246127, 'f983ff'); 49 | oneTurn(-0.0000609755516052246128, 'fbbf0ff80000000001'); 50 | oneTurn(0.00006103515625, 'f90400'); 51 | oneTurn(0.00006103515625005551, 'fb3f10000000001000'); 52 | oneTurn(1.401298464324817e-45, 'fa00000001'); 53 | oneTurn(0.00006109476089477539, 'f90401'); 54 | 55 | console.log(errorCount ? "\n\nThere were errors :(" : "\n\n SUCCESSFUL"); 56 | -------------------------------------------------------------------------------- /test/buggor.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////// 2 | // // 3 | // CBOR JavaScript API // 4 | // // 5 | // Defines a single global object CBOR to (in some way) mimic the JSON object. // 6 | // Determinisic encoding aligned with Appendix A and 4.2.2 Rule 2 of RFC 8949. // 7 | // Author: Anders Rundgren (https://github.com/cyberphone) // 8 | ///////////////////////////////////////////////////////////////////////////////// 9 | 10 | class CBOR { 11 | 12 | // Super class for all CBOR types. 13 | static #CBORObject = class { 14 | 15 | // Overridden getter in derived classes. 16 | _get = function() {}; 17 | 18 | constructor() {} 19 | 20 | getInt = function() { 21 | if (this instanceof CBOR.BigInt) { 22 | // During decoding, integers outside of Number.MAX_SAFE_INTEGER 23 | // automatically get "BigInt" representation. 24 | throw Error("Integer is outside of Number.MAX_SAFE_INTEGER, use getBigInt()"); 25 | } 26 | return this.#checkTypeAndGetValue(CBOR.Int); 27 | } 28 | 29 | getString = function() { 30 | return this.#checkTypeAndGetValue(CBOR.String); 31 | } 32 | 33 | getBytes = function() { 34 | return this.#checkTypeAndGetValue(CBOR.Bytes); 35 | } 36 | 37 | getFloat = function() { 38 | return this.#checkTypeAndGetValue(CBOR.Float); 39 | } 40 | 41 | getBool = function() { 42 | return this.#checkTypeAndGetValue(CBOR.Bool); 43 | } 44 | 45 | getNull = function() { 46 | return this instanceof CBOR.Null; 47 | } 48 | 49 | getBigInt = function() { 50 | if (this instanceof CBOR.Int) { 51 | return BigInt(this._get()); 52 | } 53 | return this.#checkTypeAndGetValue(CBOR.BigInt); 54 | } 55 | 56 | getArray = function() { 57 | return this.#checkTypeAndGetValue(CBOR.Array); 58 | } 59 | 60 | getMap = function() { 61 | return this.#checkTypeAndGetValue(CBOR.Map); 62 | } 63 | 64 | getTag = function() { 65 | return this.#checkTypeAndGetValue(CBOR.Tag); 66 | } 67 | 68 | #checkTypeAndGetValue = function(className) { 69 | if (!(this instanceof className)) { 70 | throw Error("Invalid object for this method: CBOR." + this.constructor.name); 71 | } 72 | return this._get(); 73 | } 74 | } 75 | 76 | static #MT_UNSIGNED = 0x00; 77 | static #MT_NEGATIVE = 0x20; 78 | static #MT_BYTES = 0x40; 79 | static #MT_STRING = 0x60; 80 | static #MT_ARRAY = 0x80; 81 | static #MT_MAP = 0xa0; 82 | static #MT_TAG = 0xc0; 83 | static #MT_BIG_UNSIGNED = 0xc2; 84 | static #MT_BIG_NEGATIVE = 0xc3; 85 | static #MT_FALSE = 0xf4; 86 | static #MT_TRUE = 0xf5; 87 | static #MT_NULL = 0xf6; 88 | static #MT_FLOAT16 = 0xf9; 89 | static #MT_FLOAT32 = 0xfa; 90 | static #MT_FLOAT64 = 0xfb; 91 | 92 | static #RANGES = [0xff, 0xffff, 0xffffffff]; 93 | 94 | static #SPECIAL_CHARACTERS = [ 95 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 96 | 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 'b', 't', 'n', 1 , 'f', 'r', 1 , 1 , 97 | 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 98 | 0 , 0 , '"', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 99 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 100 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 101 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , '\\']; 102 | 103 | constructor() { 104 | throw Error("CBOR cannot be instantiated"); 105 | } 106 | 107 | /////////////////////////// 108 | // CBOR.Int // 109 | /////////////////////////// 110 | 111 | static Int = class extends CBOR.#CBORObject { 112 | 113 | #int; 114 | 115 | // Note that for integers with a magnitude above 2^53 - 1, "BigInt" must be used. 116 | constructor(int) { 117 | super(); 118 | this.#int = CBOR.#intCheck(int); 119 | } 120 | 121 | encode = function() { 122 | let tag; 123 | let n = this.#int; 124 | if (n < 0) { 125 | tag = CBOR.#MT_NEGATIVE; 126 | n = -n - 1; 127 | } else { 128 | tag = CBOR.#MT_UNSIGNED; 129 | } 130 | return CBOR.#encodeTagAndN(tag, n); 131 | } 132 | 133 | toString = function() { 134 | return this.#int.toString(); 135 | } 136 | 137 | _get = function() { 138 | return this.#int; 139 | } 140 | } 141 | 142 | /////////////////////////// 143 | // CBOR.BigInt // 144 | /////////////////////////// 145 | 146 | static BigInt = class extends CBOR.#CBORObject { 147 | 148 | #bigInt; 149 | 150 | constructor(bigInt) { 151 | super(); 152 | this.#bigInt = CBOR.#typeCheck(bigInt, 'bigint'); 153 | } 154 | 155 | encode = function() { 156 | let tag; 157 | let value = this.#bigInt 158 | if (value < 0) { 159 | tag = CBOR.#MT_NEGATIVE; 160 | value = ~value; 161 | } else { 162 | tag = CBOR.#MT_UNSIGNED; 163 | } 164 | 165 | // Somewhat awkward code for converting BigInt to Uint8Array. 166 | let array = []; 167 | let temp = BigInt(value); 168 | do { 169 | array.push(Number(temp & 255n)); 170 | temp /= 256n; 171 | } while (temp != 0n); 172 | let length = array.length; 173 | // Prepare for "Int" encoding (1, 2, 4, 8). Only 3, 5, 6, and 7 need an action. 174 | while (length < 8 && length > 2 && length != 4) { 175 | array.push(0); 176 | length++; 177 | } 178 | let byteArray = new Uint8Array(array.reverse()); 179 | 180 | // Does this value qualify as a "BigInt"? 181 | if (length <= 8) { 182 | // Apparently not, encode it as "Int". 183 | if (length == 1 && byteArray[0] < 24) { 184 | return new Uint8Array([tag | byteArray[0]]); 185 | } 186 | let modifier = 24; 187 | while (length >>= 1) { 188 | modifier++; 189 | } 190 | return CBOR.#addArrays(new Uint8Array([tag | modifier]), byteArray); 191 | } 192 | // True "BigInt". 193 | return CBOR.#addArrays(new Uint8Array([tag == CBOR.#MT_NEGATIVE ? 194 | CBOR.#MT_BIG_NEGATIVE : CBOR.#MT_BIG_UNSIGNED]), 195 | new CBOR.Bytes(byteArray).encode()); 196 | } 197 | 198 | toString = function() { 199 | return this.#bigInt.toString(); 200 | } 201 | 202 | _get = function() { 203 | return this.#bigInt; 204 | } 205 | } 206 | 207 | 208 | /////////////////////////// 209 | // CBOR.Float // 210 | /////////////////////////// 211 | 212 | static Float = class extends CBOR.#CBORObject { 213 | 214 | #float; 215 | #encoded; 216 | #tag; 217 | 218 | constructor(float) { 219 | super(); 220 | this.#float = CBOR.#typeCheck(float, 'number'); 221 | // Begin catching the F16 edge cases. 222 | this.#tag = CBOR.#MT_FLOAT16; 223 | if (Number.isNaN(float)) { 224 | this.#encoded = this.#f16Encoding(0x7e00); 225 | } else if (!Number.isFinite(float)) { 226 | this.#encoded = this.#f16Encoding(float < 0 ? 0xfc00 : 0x7c00); 227 | } else if (Math.abs(float) == 0) { 228 | this.#encoded = this.#f16Encoding(Object.is(float,-0) ? 0x8000 : 0x0000); 229 | } else { 230 | // It is apparently a genuine number. 231 | // The following code depends on that Math.fround works as expected. 232 | let f32 = Math.fround(float); 233 | let u8; 234 | let f32exp; 235 | let f32signif; 236 | while (true) { // "goto" surely beats quirky loop/break/return/flag constructs... 237 | if (f32 == float) { 238 | // Nothing was lost during the conversion, F32 or F16 is on the menu. 239 | this.#tag = CBOR.#MT_FLOAT32; 240 | u8 = this.#f64Encoding(f32); 241 | f32exp = ((u8[0] & 0x7f) << 4) + ((u8[1] & 0xf0) >> 4) - 1023 + 127; 242 | if (u8[4] & 0x1f || u8[5] || u8[6] || u8[7]) { 243 | console.log(u8.toString()); 244 | throw Error("unexpected fraction: " + f32); 245 | } 246 | f32signif = ((u8[1] & 0x0f) << 19) + (u8[2] << 11) + (u8[3] << 3) + (u8[4] >> 5) 247 | // if (Math.abs(f32) == 5.960465188081798e-8) console.log("b=" + toBin(u8) + " e=" + (((u8[0] & 0x7f) << 4) + ((u8[1] & 0xf0) >> 4)) + " ec=" + f32exp + " f32signif=" + f32signif + " s=" + ((u8[0] & 0x80) * 16777216)); 248 | if (f32exp <= 0) { 249 | // The implicit "1" becomes explicit using subnormal representation. 250 | f32signif += 1 << 23; 251 | // Always perform at least one turn. 252 | f32exp--; 253 | do { 254 | if ((f32signif & 1) != 0) { 255 | throw Error("unexpected offscale: " + f32); 256 | } 257 | f32signif >>= 1; 258 | } while (++f32exp < 0); 259 | } 260 | // If it is a subnormal F32 or if F16 would lose precision, stick to F32. 261 | if (f32exp == 0 || f32signif & 0x1fff) { 262 | console.log('@@@ skip ' + (f32exp ? "f32prec" : "f32denorm")); 263 | break; 264 | } 265 | // Arrange for F16. 266 | let f16exp = f32exp - 127 + 15; 267 | let f16signif = f32signif >> 13; 268 | // If too large for F16, stick to F32. 269 | if (f16exp > 30) { 270 | console.log("@@@ skip above f16exp=" + f16exp); 271 | break; 272 | } 273 | // Finally, is this value too small for F16? 274 | if (f16exp <= 0) { 275 | // The implicit "1" becomes explicit using subnormal representation. 276 | f16signif += 1 << 10; 277 | // Always perform at least one turn. 278 | f16exp--; 279 | do { 280 | // Losing bits is not an option. 281 | if ((f16signif & 1) != 0) { 282 | f16signif = 0; 283 | console.log("@@@ skip under f16"); 284 | break; 285 | } 286 | f16signif >>= 1; 287 | } while (++f16exp < 0); 288 | // If too small for F16, stick to F32. 289 | if (f16signif == 0) { 290 | break; 291 | } 292 | console.log("@@@ succeeded f16 denorm"); 293 | } 294 | // 16 bits is all you need. 295 | this.#tag = CBOR.#MT_FLOAT16; 296 | let f16bin = 297 | // Put sign bit in position. 298 | ((u8[0] & 0x80) << 8) + 299 | // Exponent. Put it in front of significand. 300 | (f16exp << 10) + 301 | // Significand. 302 | f16signif; 303 | this.#encoded = this.#f16Encoding(f16bin); 304 | } else { 305 | // Converting to F32 returned a truncated result. Full 64-bit float is required. 306 | this.#tag = CBOR.#MT_FLOAT64; 307 | this.#encoded = this.#f64Encoding(float); 308 | } 309 | // Common F16 and F64 return point. 310 | return; 311 | } 312 | // break: 32 bits are apparently needed for maintaining magnitude and precision. 313 | let f32bin = 314 | // Put sign bit in position. Why not << 24? JS shift doesn't work above 2^31... 315 | ((u8[0] & 0x80) * 0x1000000) + 316 | // Exponent. Put it in front of significand. 317 | (f32exp << 23) + 318 | // Significand. 319 | f32signif; 320 | this.#encoded = CBOR.#addArrays(this.#f16Encoding(f32bin / 0x10000), 321 | this.#f16Encoding(f32bin & 0xffff)); 322 | } 323 | } 324 | 325 | encode = function() { 326 | return CBOR.#addArrays(new Uint8Array([this.#tag]), this.#encoded); 327 | } 328 | 329 | toString = function() { 330 | return this.#float.toString(); 331 | } 332 | 333 | _get = function() { 334 | return this.#float; 335 | } 336 | 337 | #f16Encoding = function(int16) { 338 | return new Uint8Array([int16 / 256, int16 % 256]); 339 | } 340 | 341 | #f64Encoding = function(number) { 342 | const buffer = new ArrayBuffer(8); 343 | new DataView(buffer).setFloat64(0, number, false); 344 | return [].slice.call(new Uint8Array(buffer)) 345 | } 346 | } 347 | 348 | /////////////////////////// 349 | // CBOR.String // 350 | /////////////////////////// 351 | 352 | static String = class extends CBOR.#CBORObject { 353 | 354 | #string; 355 | 356 | constructor(string) { 357 | super(); 358 | this.#string = CBOR.#typeCheck(string, 'string'); 359 | } 360 | 361 | encode = function() { 362 | let utf8 = new TextEncoder().encode(this.#string); 363 | return CBOR.#addArrays(CBOR.#encodeTagAndN(CBOR.#MT_STRING, utf8.length), utf8); 364 | } 365 | 366 | toString = function() { 367 | let buffer = '"'; 368 | for (let q = 0; q < this.#string.length; q++) { 369 | let c = this.#string.charCodeAt(q); 370 | if (c <= 0x5c) { 371 | let convertedCharacter; 372 | if ((convertedCharacter = CBOR.#SPECIAL_CHARACTERS[c]) != 0) { 373 | buffer += '\\'; 374 | if (convertedCharacter == 1) { 375 | buffer += 'u00' + CBOR.#twoHex(c); 376 | } else { 377 | buffer += convertedCharacter; 378 | } 379 | continue; 380 | } 381 | } 382 | buffer += String.fromCharCode(c); 383 | } 384 | return buffer + '"'; 385 | } 386 | 387 | _get = function() { 388 | return this.#string; 389 | } 390 | } 391 | 392 | /////////////////////////// 393 | // CBOR.Bytes // 394 | /////////////////////////// 395 | 396 | static Bytes = class extends CBOR.#CBORObject { 397 | 398 | #bytes; 399 | 400 | constructor(bytes) { 401 | super(); 402 | this.#bytes = CBOR.#bytesCheck(bytes); 403 | } 404 | 405 | encode = function() { 406 | return CBOR.#addArrays(CBOR.#encodeTagAndN(CBOR.#MT_BYTES, this.#bytes.length), this.#bytes); 407 | } 408 | 409 | toString = function() { 410 | return "h'" + CBOR.toHex(this.#bytes) + "'"; 411 | } 412 | 413 | _get = function() { 414 | return this.#bytes; 415 | } 416 | } 417 | 418 | /////////////////////////// 419 | // CBOR.Bool // 420 | /////////////////////////// 421 | 422 | static Bool = class extends CBOR.#CBORObject { 423 | 424 | #bool; 425 | 426 | constructor(bool) { 427 | super(); 428 | this.#bool = CBOR.#typeCheck(bool, 'boolean'); 429 | } 430 | 431 | encode = function() { 432 | return new Uint8Array([this.#bool ? CBOR.#MT_TRUE : CBOR.#MT_FALSE]); 433 | } 434 | 435 | toString = function() { 436 | return this.#bool.toString(); 437 | } 438 | 439 | _get = function() { 440 | return this.#bool; 441 | } 442 | } 443 | 444 | /////////////////////////// 445 | // CBOR.Null // 446 | /////////////////////////// 447 | 448 | static Null = class extends CBOR.#CBORObject { 449 | 450 | encode = function() { 451 | return new Uint8Array([CBOR.#MT_NULL]); 452 | } 453 | 454 | toString = function() { 455 | return 'null'; 456 | } 457 | } 458 | 459 | /////////////////////////// 460 | // CBOR.Array // 461 | /////////////////////////// 462 | 463 | static Array = class extends CBOR.#CBORObject { 464 | 465 | #array = []; 466 | 467 | add = function(value) { 468 | this.#array.push(CBOR.#cborArguentCheck(value)); 469 | return this; 470 | } 471 | 472 | get = function(index) { 473 | index = CBOR.#intCheck(index); 474 | if (index < 0 || index >= this.#array.length) { 475 | throw Error("Array index out of range: " + index); 476 | } 477 | return this.#array[index]; 478 | } 479 | 480 | toArray = function() { 481 | let array = []; 482 | this.#array.forEach(element => array.push(element)); 483 | return array; 484 | } 485 | 486 | encode = function() { 487 | let encoded = CBOR.#encodeTagAndN(CBOR.#MT_ARRAY, this.#array.length); 488 | this.#array.forEach(value => { 489 | encoded = CBOR.#addArrays(encoded, value.encode()); 490 | }); 491 | return encoded; 492 | } 493 | 494 | toString = function(cborPrinter) { 495 | let buffer = '['; 496 | let notFirst = false; 497 | this.#array.forEach(value => { 498 | if (notFirst) { 499 | buffer += ', '; 500 | } 501 | notFirst = true; 502 | buffer += value.toString(cborPrinter); 503 | }); 504 | return buffer + ']'; 505 | } 506 | 507 | size = function() { 508 | return this.#array.length; 509 | } 510 | 511 | _get = function() { 512 | return this; 513 | } 514 | } 515 | 516 | /////////////////////////// 517 | // CBOR.Map // 518 | /////////////////////////// 519 | 520 | static Map = class extends CBOR.#CBORObject { 521 | 522 | #root; 523 | #lastEntry; 524 | #deterministicMode = false; 525 | 526 | set = function(key, value) { 527 | let newEntry = {}; 528 | newEntry.key = this.#getKey(key); 529 | newEntry.value = CBOR.#cborArguentCheck(value); 530 | newEntry.encodedKey = key.encode(); 531 | newEntry.next = null; 532 | if (this.#root) { 533 | // Second key etc. 534 | if (this.#deterministicMode) { 535 | // Normal case for parsing. 536 | let diff = CBOR.#compare(this.#lastEntry, newEntry.encodedKey); 537 | if (diff >= 0) { 538 | throw Error((diff == 0 ? 539 | "Duplicate: " : "Non-deterministic order: ") + key.toString()); 540 | } 541 | this.#lastEntry.next = newEntry; 542 | } else { 543 | // Programmatically created key or the result of unconstrained parsing. 544 | // Then we need to test and sort (always produce deterministic CBOR). 545 | let precedingEntry = null; 546 | let diff = 0; 547 | for (let entry = this.#root; entry; entry = entry.next) { 548 | diff = CBOR.#compare(entry, newEntry.encodedKey); 549 | if (diff == 0) { 550 | throw Error("Duplicate: " + key); 551 | } 552 | if (diff > 0) { 553 | // New key is less than a current entry. 554 | if (precedingEntry == null) { 555 | // Less than root, means the root must be redefined. 556 | newEntry.next = this.#root; 557 | this.#root = newEntry; 558 | } else { 559 | // Somewhere above root. Insert after preceding entry. 560 | newEntry.next = entry; 561 | precedingEntry.next = newEntry; 562 | } 563 | // Done, break out of the loop. 564 | break; 565 | } 566 | // No luck in this round, continue searching. 567 | precedingEntry = entry; 568 | } 569 | // Biggest key so far, insert at the end. 570 | if (diff < 0) { 571 | precedingEntry.next = newEntry; 572 | } 573 | } 574 | } else { 575 | // First key, take it as is. 576 | this.#root = newEntry; 577 | } 578 | this.#lastEntry = newEntry; 579 | return this; 580 | } 581 | 582 | #getKey = function(key) { 583 | return CBOR.#cborArguentCheck(key); 584 | } 585 | 586 | #missingKey = function(key) { 587 | throw Error("Missing key: " + key); 588 | } 589 | 590 | #lookup(key, mustExist) { 591 | let encodedKey = this.#getKey(key).encode(); 592 | for (let entry = this.#root; entry; entry = entry.next) { 593 | if (CBOR.#compare(entry, encodedKey) == 0) { 594 | return entry; 595 | } 596 | } 597 | if (mustExist) { 598 | this.#missingKey(key); 599 | } 600 | return null; 601 | } 602 | 603 | get = function(key) { 604 | return this.#lookup(key, true).value; 605 | } 606 | 607 | getConditionally = function(key, defaultValue) { 608 | let entry = this.#lookup(key, false); 609 | defaultValue = CBOR.#cborArguentCheck(defaultValue); 610 | return entry == null ? defaultValue : entry.value; 611 | } 612 | 613 | getKeys = function() { 614 | let keys = []; 615 | for (let entry = this.#root; entry; entry = entry.next) { 616 | keys.push(entry.key); 617 | } 618 | return keys; 619 | } 620 | 621 | remove = function(key) { 622 | let encodedKey = this.#getKey(key).encode(); 623 | let precedingEntry = null; 624 | for (let entry = this.#root; entry; entry = entry.next) { 625 | if (CBOR.#compare(entry, encodedKey) == 0) { 626 | if (precedingEntry == null) { 627 | // Remove root key. It may be alone. 628 | this.#root = entry.next; 629 | } else { 630 | // Remove key somewhere above root. 631 | precedingEntry.next = entry.next; 632 | } 633 | return entry.value; 634 | } 635 | precedingEntry = entry; 636 | } 637 | this.#missingKey(key); 638 | } 639 | 640 | containsKey = function(key) { 641 | return this.#lookup(key, false) != null; 642 | } 643 | 644 | encode = function() { 645 | let q = 0; 646 | let encoded = new Uint8Array(); 647 | for (let entry = this.#root; entry; entry = entry.next) { 648 | q++; 649 | encoded = CBOR.#addArrays(encoded, 650 | CBOR.#addArrays(entry.key.encode(), entry.value.encode())); 651 | } 652 | return CBOR.#addArrays(CBOR.#encodeTagAndN(CBOR.#MT_MAP, q), encoded); 653 | } 654 | 655 | toString = function(cborPrinter) { 656 | if (cborPrinter == undefined) { 657 | cborPrinter = new CBOR.#Printer(); 658 | } 659 | let notFirst = false; 660 | let buffer = cborPrinter.beginMap(); 661 | for (let entry = this.#root; entry; entry = entry.next) { 662 | if (notFirst) { 663 | buffer += ','; 664 | } 665 | notFirst = true; 666 | buffer += cborPrinter.newlineAndIndent(); 667 | buffer += entry.key.toString(cborPrinter) + ': ' + entry.value.toString(cborPrinter); 668 | } 669 | return buffer + cborPrinter.endMap(notFirst); 670 | } 671 | 672 | _get = function() { 673 | return this; 674 | } 675 | } 676 | 677 | /////////////////////////// 678 | // CBOR.Tag // 679 | /////////////////////////// 680 | 681 | static Tag = class extends CBOR.#CBORObject { 682 | 683 | #tagNumber; 684 | #object; 685 | 686 | constructor(tagNumber, object) { 687 | super(); 688 | this.#tagNumber = CBOR.#intCheck(tagNumber); 689 | if (tagNumber < 0) { 690 | throw Error("Tag is negative"); 691 | } 692 | this.#object = CBOR.#cborArguentCheck(object); 693 | } 694 | 695 | encode = function() { 696 | return CBOR.#addArrays(CBOR.#encodeTagAndN(CBOR.#MT_TAG, this.#tagNumber), 697 | this.#object.encode()); 698 | } 699 | 700 | toString = function(cborPrinter) { 701 | return this.#tagNumber.toString() + '(' + this.#object.toString(cborPrinter) + ')'; 702 | } 703 | 704 | _get = function() { 705 | return this; 706 | } 707 | } 708 | 709 | 710 | static #_decoder = class { 711 | 712 | constructor(cbor, 713 | sequenceFlag, 714 | acceptNonDeterministic, 715 | constrainedMapKeys) { 716 | this.cbor = cbor; 717 | this.counter = 0; 718 | this.atFirstByte = true; 719 | this.sequenceFlag = sequenceFlag; 720 | this.deterministicMode = !acceptNonDeterministic; 721 | this.constrainedMapKeys = constrainedMapKeys; 722 | } 723 | 724 | readByte = function() { 725 | if (this.counter >= this.cbor.length) { 726 | if (this.sequenceFlag && this.atFirstByte) { 727 | return CBOR.#MT_NULL; 728 | } 729 | throw Error("Reading past end of buffer"); 730 | } 731 | this.atFirstByte = false; 732 | return this.cbor[this.counter++]; 733 | } 734 | 735 | readBytes = function (length) { 736 | let result = new Uint8Arry(length); 737 | let q = -1; 738 | while (++q < length) { 739 | result[q] = readByte(); 740 | } 741 | return result; 742 | } 743 | /* 744 | 745 | private CBORFloat checkDoubleConversion(int tag, long bitFormat, long rawDouble) 746 | { 747 | CBORFloat value = new CBORFloat(Double.longBitsToDouble(rawDouble)); 748 | if ((value.tag != tag || value.bitFormat != bitFormat) && deterministicMode) { 749 | reportError(String.format(STDERR_NON_DETERMINISTIC_FLOAT + "%2x", tag)); 750 | } 751 | return value; 752 | } 753 | */ 754 | unsupportedTag = function(tag) { 755 | throw Error("Unsupported tag: " + CBOR.#twoHex(tag)); 756 | } 757 | 758 | rangeLimitedBigInt = function(bigInt) { 759 | if (bigInt > 0xffffffffn) { 760 | throw Error("Length limited to 0xffffffff"); 761 | } 762 | return Number(bigInt); 763 | } 764 | 765 | getObject = function() { 766 | let tag = this.readByte(); 767 | console.log("Get: "+ tag); 768 | 769 | // Begin with CBOR types that are uniquely defined by the tag byte. 770 | switch (tag) { 771 | case CBOR.#MT_BIG_NEGATIVE: 772 | case CBOR.#MT_BIG_UNSIGNED: 773 | let byteArray = this.getObject().getBytes(); 774 | if ((byteArray.length == 0 || byteArray[0] == 0 || byteArray.length <= 8) && 775 | this.deterministicMode) { 776 | throw Error("Non-deterministic big integer encoding"); 777 | } 778 | let bigInt = BigInt(0); 779 | byteArray.forEach(byte => { 780 | bigInt <<= 8; 781 | bigInt += BigInt(byte); 782 | }); 783 | if (tag == MT_BIG_NEGATIVE) { 784 | bigInt = ~bigInt; 785 | } 786 | return new CBOR.BigInt(bigInt); 787 | /* 788 | case CBOR.#MT_FLOAT16: 789 | let float16 = readNumber(2); 790 | let unsignedf16 = float16 & ~FLOAT16_NEG_ZERO; 791 | 792 | // Begin with the edge cases. 793 | 794 | if ((unsignedf16 & FLOAT16_POS_INFINITY) == FLOAT16_POS_INFINITY) { 795 | // Special "number" 796 | f64Bin = (unsignedf16 == FLOAT16_POS_INFINITY) ? 797 | // Non-deterministic representations of NaN will be flagged later. 798 | // NaN "signaling" is not supported, "quiet" NaN is all there is. 799 | 800 | FLOAT64_POS_INFINITY : FLOAT64_NOT_A_NUMBER; 801 | 802 | } else if (unsignedf16 == 0) { 803 | f64Bin = FLOAT64_ZERO; 804 | } else { 805 | 806 | // It is a "regular" non-zero number. 807 | 808 | // Get the bare (but still biased) float16 exponent. 809 | let exponent = (unsignedf16 >> FLOAT16_SIGNIFICAND_SIZE); 810 | // Get the float16 significand bits. 811 | let significand = unsignedf16; 812 | if (exponent == 0) { 813 | // Subnormal float16 - In float64 that must translate to normalized. 814 | exponent++; 815 | do { 816 | exponent--; 817 | significand <<= 1; 818 | // Continue until the implicit "1" is in the proper position. 819 | } while ((significand & (1 << FLOAT16_SIGNIFICAND_SIZE)) == 0); 820 | } 821 | // significand & ((1 << FLOAT16_SIGNIFICAND_SIZE) - 1); 822 | f64Bin = mapValues(exponent + FLOAT64_EXPONENT_BIAS - FLOAT16_EXPONENT_BIAS, 823 | significand, FLOAT16_SIGNIFICAND_SIZE); 824 | mapVau 825 | unsignedResult = 826 | // Exponent. Set the proper bias and put result in front of significand. 827 | ((exponent + (FLOAT64_EXPONENT_BIAS - FLOAT16_EXPONENT_BIAS)) 828 | << FLOAT64_SIGNIFICAND_SIZE) + 829 | // Significand. Remove everything above. 830 | (significand & ((1l << FLOAT64_SIGNIFICAND_SIZE) - 1)); 831 | } 832 | return checkDoubleConversion(tag, 833 | float16, 834 | f64Bin, 835 | // Put sign bit in position. 836 | ((float16 & FLOAT16_NEG_ZERO) << (64 - 16))); 837 | 838 | case CBOR.#MT_FLOAT32: 839 | long float32 = getLongFromBytes(4); 840 | return checkDoubleConversion(tag, 841 | float32, 842 | Double.doubleToLongBits( 843 | Float.intBitsToFloat((int)float32))); 844 | 845 | case CBOR.#MT_FLOAT64: 846 | long float64 = getLongFromBytes(8); 847 | return checkDoubleConversion(tag, float64, float64); 848 | */ 849 | case CBOR.#MT_NULL: 850 | return new CBOR.Null(); 851 | 852 | case CBOR.#MT_TRUE: 853 | case CBOR.#MT_FALSE: 854 | return new CBOR.Bool(tag == CBOR.#MT_TRUE); 855 | } 856 | // Then decode CBOR types that blend length of data in the tag byte. 857 | let n = tag & 0x1f; 858 | let bigN = 0n; 859 | if (n > 27n) { 860 | this.unsupportedTag(tag); 861 | } 862 | if (n > 23) { 863 | // For 1, 2, 4, and 8 byte N. 864 | let diff = n - 24; 865 | let q = 1 << diff; 866 | while (--q >= 0) { 867 | bigN <<= 8n; 868 | bigN |= BigInt(this.readByte()); 869 | } 870 | // If the upper half (for 2, 4, 8 byte N) of N or a single byte 871 | // N is zero, a shorter variant should have been used. 872 | // In addition, N must be > 23. 873 | if ((bigN < 24n || (--diff >= 0 && BigInt(~CBOR.#RANGES[diff]) & bigN)) && 874 | this.deterministicMode) { 875 | throw Error("Non-deterministic integer encoding"); 876 | } 877 | } else { 878 | bigN = BigInt(n); 879 | } 880 | console.log("N=" + bigN); 881 | // N successfully decoded, now switch on major type (upper three bits). 882 | switch (tag & 0xe0) { 883 | case CBOR.#MT_TAG: 884 | let tagData = getObject(); 885 | /* 886 | if (bigN == CBORTag.RESERVED_TAG_COTX) { 887 | let holder = tagData.getArray(2); 888 | if (holder.get(0).getType() != CBORTypes.TEXT_STRING) { 889 | reportError("Tag syntax " + CBORTag.RESERVED_TAG_COTX + 890 | "([\"string\", CBOR object]) expected"); 891 | } 892 | } 893 | */ 894 | return new CBOR.Tag(bigN, tagData); 895 | 896 | case CBOR.#MT_UNSIGNED: 897 | if (bigN >= BigInt(Number.MAX_SAFE_INTEGER)) { 898 | return new CBOR.BigInt(bigN); 899 | } 900 | return new CBOR.Int(Number(bigN), true); 901 | 902 | case CBOR.#MT_NEGATIVE: 903 | let bigN = ~bigN; 904 | if (bigN <= BigInt(-Number.MAX_SAFE_INTEGER)) { 905 | return new CBOR.BigInt(value); 906 | } 907 | return new CBOR.Int(Number(bigN), false); 908 | 909 | case CBOR.#MT_BYTES: 910 | return new CBOR.Bytes(this.readBytes(this.rangeLimitedBigInt(bigN))); 911 | 912 | case CBOR.#MT_STRING: 913 | return new CBOR.String(UTF8.decode(this.readBytes(this.rangeLimitedBigInt(bigN)))); 914 | 915 | case CBOR.#MT_ARRAY: 916 | let cborArray = new CBOR.Array(); 917 | for (let q = this.rangeLimitedBigInt(bigN); --q >= 0;) { 918 | cborArray.add(getObject()); 919 | } 920 | return cborArray; 921 | 922 | case CBOR.#MT_MAP: 923 | let cborMap = new CBOR.Map(); 924 | cborMap.deterministicMode = deterministicMode; 925 | cborMap.constrainedKeys = constrainedMapKeys; 926 | for (let q = this.rangeLimitedBigInt(bigN); --q >= 0;) { 927 | cborMap.set(getObject(), getObject()); 928 | } 929 | // Programmatically added elements sort automatically. 930 | cborMap.deterministicMode = false; 931 | return cborMap; 932 | 933 | default: 934 | this.unsupportedTag(tag); 935 | } 936 | } 937 | } 938 | 939 | /////////////////////////// 940 | // CBOR.decode() // 941 | /////////////////////////// 942 | 943 | static decode = function(cbor) { 944 | let decoder = new CBOR.#_decoder(CBOR.#bytesCheck(cbor), false, false, false); 945 | return decoder.getObject(); 946 | } 947 | 948 | /////////////////////////// 949 | // Support Methods // 950 | /////////////////////////// 951 | 952 | static #encodeTagAndN = function(majorType, n) { 953 | let modifier = n; 954 | let length = 0; 955 | if (n > 23) { 956 | modifier = 24; 957 | length = 1; 958 | let q = 0; 959 | while (q < 3 && n > CBOR.#RANGES[q++]) { 960 | modifier++; 961 | length <<= 1; 962 | } 963 | } 964 | let encoded = new Uint8Array(length + 1); 965 | encoded[0] = majorType | modifier; 966 | while (length > 0) { 967 | encoded[length--] = n; 968 | n /= 256; 969 | } 970 | return encoded; 971 | } 972 | 973 | static #addArrays = function(a1, a2) { 974 | let res = new Uint8Array(a1.length + a2.length); 975 | let q = 0; 976 | while (q < a1.length) { 977 | res[q] = a1[q++]; 978 | } 979 | for (let i = 0; i < a2.length; i++) { 980 | res[q + i] = a2[i]; 981 | } 982 | return res; 983 | } 984 | 985 | static #compare = function(entry, testKey) { 986 | let encodedKey = entry.encodedKey; 987 | let minIndex = Math.min(encodedKey.length, testKey.length); 988 | for (let i = 0; i < minIndex; i++) { 989 | let diff = encodedKey[i] - testKey[i]; 990 | if (diff != 0) { 991 | return diff; 992 | } 993 | } 994 | return encodedKey.length - testKey.length; 995 | } 996 | 997 | static #bytesCheck = function(bytes) { 998 | if (bytes instanceof Uint8Array) { 999 | return bytes; 1000 | } 1001 | throw Error("Argument is not an 'Uint8Array'"); 1002 | } 1003 | 1004 | static #typeCheck = function(object, type) { 1005 | if (typeof object != type) { 1006 | throw Error("Argument is not a '" + type + "'"); 1007 | } 1008 | return object; 1009 | } 1010 | 1011 | static #intCheck = function(int) { 1012 | CBOR.#typeCheck(int, 'number'); 1013 | if (!Number.isSafeInteger(int)) { 1014 | throw Error(Number.isInteger(int) ? 1015 | "Argument is outside of Number.MAX_SAFE_INTEGER" : "Argument is not an integer"); 1016 | } 1017 | return int; 1018 | } 1019 | 1020 | static #Printer = class { 1021 | indentationLevel = 0; 1022 | 1023 | beginMap = function() { 1024 | this.indentationLevel++; 1025 | return '{'; 1026 | } 1027 | 1028 | newlineAndIndent = function() { 1029 | let buffer = '\n'; 1030 | for (let i = 0; i < this.indentationLevel; i++) { 1031 | buffer += ' '; 1032 | } 1033 | return buffer; 1034 | } 1035 | 1036 | endMap = function(notEmpty) { 1037 | this.indentationLevel--; 1038 | if (notEmpty) { 1039 | return this.newlineAndIndent() + '}'; 1040 | } 1041 | return '}'; 1042 | } 1043 | } 1044 | 1045 | static #oneHex = function (digit) { 1046 | return String.fromCharCode(digit < 10 ? (48 + digit) : (87 + digit)); 1047 | } 1048 | 1049 | static #twoHex = function(byte) { 1050 | return CBOR.#oneHex(byte / 16) + CBOR.#oneHex(byte % 16); 1051 | } 1052 | 1053 | static #cborArguentCheck = function(value) { 1054 | if (value instanceof CBOR.#CBORObject) { 1055 | return value; 1056 | } 1057 | throw Error(value ? "Argument is not a CBOR object: " + value.constructor.name : "'null'"); 1058 | } 1059 | 1060 | static toHex = function (bin) { 1061 | let result = ''; 1062 | for (let i = 0; i < bin.length; i++) { 1063 | result += CBOR.#twoHex(bin[i]); 1064 | } 1065 | return result; 1066 | } 1067 | 1068 | } 1069 | 1070 | // To be DELETED 1071 | 1072 | toBin = function(bin) { 1073 | let exppos = bin.length == 8 ? 4 : 7; 1074 | let res = ''; 1075 | for (let q = 0; q < bin.length; q++) { 1076 | for (let s = 7; s >= 0; s--) { 1077 | res += String.fromCharCode(48 + ((bin[q] >> s) & 1)); 1078 | if ((q == 0 && s == 7) || (q == 1 && s == exppos)) { 1079 | res += ' '; 1080 | } 1081 | } 1082 | } 1083 | return res; 1084 | } 1085 | 1086 | let bigInt = BigInt("0"); 1087 | let cbor = new CBOR.BigInt(bigInt).encode(); 1088 | console.log("Encoded: " + CBOR.toHex(cbor)); 1089 | console("Decoded: " + CBOR.decode(cbor).getBigInt()); 1090 | -------------------------------------------------------------------------------- /test/save-cbor-js-api.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////// 2 | // // 3 | // CBOR JavaScript API // 4 | // // 5 | // Defines a single global object CBOR to (in some way) mimic the JSON object. // 6 | // Determinisic encoding aligned with Appendix A and 4.2.2 Rule 2 of RFC 8949. // 7 | // Author: Anders Rundgren (https://github.com/cyberphone) // 8 | ///////////////////////////////////////////////////////////////////////////////// 9 | 10 | 'use strict'; 11 | 12 | class CBOR { 13 | 14 | // Super class for all CBOR types. 15 | static #CBORObject = class { 16 | 17 | constructor() {} 18 | 19 | getInt = function() { 20 | if (this instanceof CBOR.BigInt) { 21 | // During decoding, integers outside of Number.MAX_SAFE_INTEGER 22 | // automatically get "BigInt" representation. 23 | throw Error("Integer is outside of Number.MAX_SAFE_INTEGER, use getBigInt()"); 24 | } 25 | return this.#checkTypeAndGetValue(CBOR.Int); 26 | } 27 | 28 | getString = function() { 29 | return this.#checkTypeAndGetValue(CBOR.String); 30 | } 31 | 32 | getBytes = function() { 33 | return this.#checkTypeAndGetValue(CBOR.Bytes); 34 | } 35 | 36 | getFloat = function() { 37 | return this.#checkTypeAndGetValue(CBOR.Float); 38 | } 39 | 40 | getBool = function() { 41 | return this.#checkTypeAndGetValue(CBOR.Bool); 42 | } 43 | 44 | getNull = function() { 45 | return this instanceof CBOR.Null; 46 | } 47 | 48 | getBigInt = function() { 49 | if (this instanceof CBOR.Int) { 50 | return BigInt(this._get()); 51 | } 52 | return this.#checkTypeAndGetValue(CBOR.BigInt); 53 | } 54 | 55 | getArray = function() { 56 | return this.#checkTypeAndGetValue(CBOR.Array); 57 | } 58 | 59 | getMap = function() { 60 | return this.#checkTypeAndGetValue(CBOR.Map); 61 | } 62 | 63 | getTag = function() { 64 | return this.#checkTypeAndGetValue(CBOR.Tag); 65 | } 66 | 67 | equals = function(object) { 68 | if (object && object instanceof CBOR.#CBORObject) { 69 | return CBOR.compareArrays(this.encode(), object.encode()) == 0; 70 | } 71 | return false; 72 | } 73 | 74 | clone = function() { 75 | return CBOR.decode(this.encode()); 76 | } 77 | 78 | // Overridden by CBOR.Int and CBOR.String 79 | constrainedKeyType = function() { 80 | return true; 81 | } 82 | 83 | #checkTypeAndGetValue = function(className) { 84 | if (!(this instanceof className)) { 85 | throw Error("Invalid method call for object: CBOR." + this.constructor.name); 86 | } 87 | return this._get(); 88 | } 89 | } 90 | 91 | static #MT_UNSIGNED = 0x00; 92 | static #MT_NEGATIVE = 0x20; 93 | static #MT_BYTES = 0x40; 94 | static #MT_STRING = 0x60; 95 | static #MT_ARRAY = 0x80; 96 | static #MT_MAP = 0xa0; 97 | static #MT_TAG = 0xc0; 98 | static #MT_BIG_UNSIGNED = 0xc2; 99 | static #MT_BIG_NEGATIVE = 0xc3; 100 | static #MT_FALSE = 0xf4; 101 | static #MT_TRUE = 0xf5; 102 | static #MT_NULL = 0xf6; 103 | static #MT_FLOAT16 = 0xf9; 104 | static #MT_FLOAT32 = 0xfa; 105 | static #MT_FLOAT64 = 0xfb; 106 | 107 | static #ESCAPE_CHARACTERS = [ 108 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 109 | 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 'b', 't', 'n', 1 , 'f', 'r', 1 , 1 , 110 | 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 111 | 0 , 0 , '"', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 112 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 113 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 114 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , '\\']; 115 | 116 | constructor() { 117 | throw Error("CBOR cannot be instantiated"); 118 | } 119 | 120 | /////////////////////////// 121 | // CBOR.Int // 122 | /////////////////////////// 123 | 124 | static Int = class extends CBOR.#CBORObject { 125 | 126 | #value; 127 | 128 | // Integers with a magnitude above 2^53 - 1, must use CBOR.BigInt. 129 | constructor(value) { 130 | super(); 131 | this.#value = CBOR.#intCheck(value); 132 | } 133 | 134 | encode = function() { 135 | let tag; 136 | let n = this.#value; 137 | if (n < 0) { 138 | tag = CBOR.#MT_NEGATIVE; 139 | n = -n - 1; 140 | } else { 141 | tag = CBOR.#MT_UNSIGNED; 142 | } 143 | return CBOR.#encodeTagAndN(tag, n); 144 | } 145 | 146 | toString = function() { 147 | return this.#value.toString(); 148 | } 149 | 150 | constrainedKeyType = function() { 151 | return false; 152 | } 153 | 154 | _get = function() { 155 | return this.#value; 156 | } 157 | } 158 | 159 | /////////////////////////// 160 | // CBOR.BigInt // 161 | /////////////////////////// 162 | 163 | static BigInt = class extends CBOR.#CBORObject { 164 | 165 | #value; 166 | 167 | // The CBOR.BigInt wrapper object implements the CBOR integer reduction algorithm. The 168 | // JavaScript "BigInt" object is used for maintaining lossless represention of large integers. 169 | constructor(value) { 170 | super(); 171 | this.#value = CBOR.#typeCheck(value, 'bigint'); 172 | } 173 | 174 | encode = function() { 175 | let tag; 176 | let value = this.#value 177 | if (value < 0) { 178 | tag = CBOR.#MT_NEGATIVE; 179 | value = ~value; 180 | } else { 181 | tag = CBOR.#MT_UNSIGNED; 182 | } 183 | return CBOR.#finishBigIntAndTag(tag, value); 184 | } 185 | 186 | toString = function() { 187 | return this.#value.toString(); 188 | } 189 | 190 | _get = function() { 191 | return this.#value; 192 | } 193 | } 194 | 195 | 196 | /////////////////////////// 197 | // CBOR.Float // 198 | /////////////////////////// 199 | 200 | static Float = class extends CBOR.#CBORObject { 201 | 202 | #value; 203 | #encoded; 204 | #tag; 205 | 206 | constructor(value) { 207 | super(); 208 | this.#value = CBOR.#typeCheck(value, 'number'); 209 | // Begin catching the F16 edge cases. 210 | this.#tag = CBOR.#MT_FLOAT16; 211 | if (Number.isNaN(value)) { 212 | this.#encoded = CBOR.#int16ToByteArray(0x7e00); 213 | } else if (!Number.isFinite(value)) { 214 | this.#encoded = CBOR.#int16ToByteArray(value < 0 ? 0xfc00 : 0x7c00); 215 | } else if (Math.abs(value) == 0) { 216 | this.#encoded = CBOR.#int16ToByteArray(Object.is(value,-0) ? 0x8000 : 0x0000); 217 | } else { 218 | // It is apparently a genuine (non-zero) number. 219 | // The following code depends on that Math.fround works as expected. 220 | let f32 = Math.fround(value); 221 | let u8; 222 | let f32exp; 223 | let f32signif; 224 | while (true) { // "goto" surely beats quirky loop/break/return/flag constructs... 225 | if (f32 == value) { 226 | // Nothing was lost during the conversion, F32 or F16 is on the menu. 227 | this.#tag = CBOR.#MT_FLOAT32; 228 | // However, JavaScript always defer to F64 for "Number". 229 | u8 = CBOR.#f64ToByteArray(value); 230 | f32exp = ((u8[0] & 0x7f) << 4) + ((u8[1] & 0xf0) >> 4) - 1023 + 127; 231 | // FOR REMOVAL 232 | if (u8[4] & 0x1f || u8[5] || u8[6] || u8[7]) { 233 | throw Error("unexpected fraction: " + f32); 234 | } 235 | f32signif = ((u8[1] & 0x0f) << 19) + (u8[2] << 11) + (u8[3] << 3) + (u8[4] >> 5) 236 | // Very small F32 numbers may require subnormal representation. 237 | if (f32exp <= 0) { 238 | // The implicit "1" becomes explicit using subnormal representation. 239 | f32signif += 1 << 23; 240 | // Always perform at least one turn. 241 | f32exp--; 242 | do { 243 | // FOR REMOVAL 244 | if ((f32signif & 1) != 0) { 245 | throw Error("unexpected offscale: " + f32); 246 | } 247 | f32signif >>= 1; 248 | } while (++f32exp < 0); 249 | } 250 | // If it is a subnormal F32 or if F16 would lose precision, stick to F32. 251 | if (f32exp == 0 || f32signif & 0x1fff) { 252 | // FOR REMOVAL 253 | console.log('@@@ skip ' + (f32exp ? "f32prec" : "f32denorm")); 254 | break; 255 | } 256 | // Arrange for F16. 257 | let f16exp = f32exp - 127 + 15; 258 | let f16signif = f32signif >> 13; 259 | // If too large for F16, stick to F32. 260 | if (f16exp > 30) { 261 | // FOR REMOVAL 262 | console.log("@@@ skip above f16exp=" + f16exp); 263 | break; 264 | } 265 | // Finally, is value too small for F16? 266 | if (f16exp <= 0) { 267 | // The implicit "1" becomes explicit using subnormal representation. 268 | f16signif += 1 << 10; 269 | // Always perform at least one turn. 270 | f16exp--; 271 | do { 272 | // Losing bits is not an option. 273 | if ((f16signif & 1) != 0) { 274 | f16signif = 0; 275 | // FOR REMOVAL 276 | console.log("@@@ skip under f16"); 277 | break; 278 | } 279 | f16signif >>= 1; 280 | } while (++f16exp < 0); 281 | // If too small for F16, stick to F32. 282 | if (f16signif == 0) { 283 | break; 284 | } 285 | // FOR REMOVAL 286 | console.log("@@@ succeeded f16 denorm"); 287 | } 288 | // FOR REMOVAL 289 | console.log("f16 exp=" + f16exp); 290 | // A rarity, 16 bits turned out being sufficient for representing value. 291 | this.#tag = CBOR.#MT_FLOAT16; 292 | let f16bin = 293 | // Put sign bit in position. 294 | ((u8[0] & 0x80) << 8) + 295 | // Exponent. Put it in front of significand. 296 | (f16exp << 10) + 297 | // Significand. 298 | f16signif; 299 | this.#encoded = CBOR.#int16ToByteArray(f16bin); 300 | } else { 301 | // Converting to F32 returned a truncated result. 302 | // Full 64-bit representation is required. 303 | this.#tag = CBOR.#MT_FLOAT64; 304 | this.#encoded = CBOR.#f64ToByteArray(value); 305 | } 306 | // Common F16 and F64 return point. 307 | return; 308 | } 309 | // Broken loop: 32 bits are apparently needed for maintaining magnitude and precision. 310 | let f32bin = 311 | // Put sign bit in position. Why not << 24? JS shift doesn't work above 2^31... 312 | ((u8[0] & 0x80) * 0x1000000) + 313 | // Exponent. Put it in front of significand (<< 23). 314 | (f32exp * 0x800000) + 315 | // Significand. 316 | f32signif; 317 | this.#encoded = CBOR.addArrays(CBOR.#int16ToByteArray(f32bin / 0x10000), 318 | CBOR.#int16ToByteArray(f32bin % 0x10000)); 319 | } 320 | } 321 | 322 | encode = function() { 323 | return CBOR.addArrays(new Uint8Array([this.#tag]), this.#encoded); 324 | } 325 | 326 | toString = function() { 327 | return this.#value.toString(); 328 | } 329 | 330 | _compare = function(decoded) { 331 | console.log("A=" + CBOR.toHex(this.#encoded) + " B=" + CBOR.toHex(decoded)); 332 | return CBOR.compareArrays(this.#encoded, decoded); 333 | } 334 | 335 | _get = function() { 336 | return this.#value; 337 | } 338 | } 339 | 340 | /////////////////////////// 341 | // CBOR.String // 342 | /////////////////////////// 343 | 344 | static String = class extends CBOR.#CBORObject { 345 | 346 | #string; 347 | 348 | constructor(string) { 349 | super(); 350 | this.#string = CBOR.#typeCheck(string, 'string'); 351 | } 352 | 353 | encode = function() { 354 | let utf8 = new TextEncoder().encode(this.#string); 355 | return CBOR.addArrays(CBOR.#encodeTagAndN(CBOR.#MT_STRING, utf8.length), utf8); 356 | } 357 | 358 | toString = function() { 359 | let buffer = '"'; 360 | for (let q = 0; q < this.#string.length; q++) { 361 | let c = this.#string.charCodeAt(q); 362 | if (c <= 0x5c) { 363 | let escapedCharacter; 364 | if (escapedCharacter = CBOR.#ESCAPE_CHARACTERS[c]) { 365 | buffer += '\\'; 366 | if (escapedCharacter == 1) { 367 | buffer += 'u00' + CBOR.#twoHex(c); 368 | } else { 369 | buffer += escapedCharacter; 370 | } 371 | continue; 372 | } 373 | } 374 | buffer += String.fromCharCode(c); 375 | } 376 | return buffer + '"'; 377 | } 378 | 379 | constrainedKeyType = function() { 380 | return false; 381 | } 382 | 383 | _get = function() { 384 | return this.#string; 385 | } 386 | } 387 | 388 | /////////////////////////// 389 | // CBOR.Bytes // 390 | /////////////////////////// 391 | 392 | static Bytes = class extends CBOR.#CBORObject { 393 | 394 | #bytes; 395 | 396 | constructor(bytes) { 397 | super(); 398 | this.#bytes = CBOR.#bytesCheck(bytes); 399 | } 400 | 401 | encode = function() { 402 | return CBOR.addArrays(CBOR.#encodeTagAndN(CBOR.#MT_BYTES, this.#bytes.length), this.#bytes); 403 | } 404 | 405 | toString = function() { 406 | return "h'" + CBOR.toHex(this.#bytes) + "'"; 407 | } 408 | 409 | _get = function() { 410 | return this.#bytes; 411 | } 412 | } 413 | 414 | /////////////////////////// 415 | // CBOR.Bool // 416 | /////////////////////////// 417 | 418 | static Bool = class extends CBOR.#CBORObject { 419 | 420 | #bool; 421 | 422 | constructor(bool) { 423 | super(); 424 | this.#bool = CBOR.#typeCheck(bool, 'boolean'); 425 | } 426 | 427 | encode = function() { 428 | return new Uint8Array([this.#bool ? CBOR.#MT_TRUE : CBOR.#MT_FALSE]); 429 | } 430 | 431 | toString = function() { 432 | return this.#bool.toString(); 433 | } 434 | 435 | _get = function() { 436 | return this.#bool; 437 | } 438 | } 439 | 440 | /////////////////////////// 441 | // CBOR.Null // 442 | /////////////////////////// 443 | 444 | static Null = class extends CBOR.#CBORObject { 445 | 446 | encode = function() { 447 | return new Uint8Array([CBOR.#MT_NULL]); 448 | } 449 | 450 | toString = function() { 451 | return 'null'; 452 | } 453 | } 454 | 455 | /////////////////////////// 456 | // CBOR.Array // 457 | /////////////////////////// 458 | 459 | static Array = class extends CBOR.#CBORObject { 460 | 461 | #elements = []; 462 | 463 | add = function(element) { 464 | this.#elements.push(CBOR.#cborArguentCheck(element)); 465 | return this; 466 | } 467 | 468 | get = function(index) { 469 | index = CBOR.#intCheck(index); 470 | if (index < 0 || index >= this.#elements.length) { 471 | throw Error("Array index out of range: " + index); 472 | } 473 | return this.#elements[index]; 474 | } 475 | 476 | toArray = function() { 477 | let array = []; 478 | this.#elements.forEach(element => array.push(element)); 479 | return array; 480 | } 481 | 482 | encode = function() { 483 | let encoded = CBOR.#encodeTagAndN(CBOR.#MT_ARRAY, this.#elements.length); 484 | this.#elements.forEach(object => { 485 | encoded = CBOR.addArrays(encoded, object.encode()); 486 | }); 487 | return encoded; 488 | } 489 | 490 | toString = function(cborPrinter) { 491 | let buffer = '['; 492 | let notFirst = false; 493 | this.#elements.forEach(object => { 494 | if (notFirst) { 495 | buffer += ', '; 496 | } 497 | notFirst = true; 498 | buffer += object.toString(cborPrinter); 499 | }); 500 | return buffer + ']'; 501 | } 502 | 503 | size = function() { 504 | return this.#elements.length; 505 | } 506 | 507 | _get = function() { 508 | return this; 509 | } 510 | } 511 | 512 | /////////////////////////// 513 | // CBOR.Map // 514 | /////////////////////////// 515 | 516 | static Map = class extends CBOR.#CBORObject { 517 | 518 | #root; 519 | #lastEntry; 520 | #numberOfEntries = 0; 521 | _constrainedKeys = false; 522 | _deterministicMode = false; 523 | 524 | static Entry = class { 525 | 526 | constructor(key, value) { 527 | this.key = key; 528 | this.encodedKey = key.encode(); 529 | this.value = value; 530 | this.next = null; 531 | } 532 | 533 | compare = function(encodedKey) { 534 | return CBOR.compareArrays(this.encodedKey, encodedKey); 535 | } 536 | } 537 | 538 | set = function(key, value) { 539 | let newEntry = new CBOR.Map.Entry(this.#getKey(key), CBOR.#cborArguentCheck(value)); 540 | if (this._constrainedKeys && key.constrainedKeyType()) { 541 | throw Error("Constrained key option disallows: " + key.constructor.name); 542 | } 543 | if (this.#root) { 544 | // Second key etc. 545 | if (this._constrainedKeys && 546 | this.#lastEntry.key.constructor.name != key.constructor.name) { 547 | throw Error("Constrained key option disallows mixing types: " + key.constructor.name); 548 | } 549 | if (this._deterministicMode) { 550 | // Normal case for parsing. 551 | let diff = this.#lastEntry.compare(newEntry.encodedKey); 552 | if (diff >= 0) { 553 | throw Error((diff ? "Non-deterministic order: " : "Duplicate: ") + key); 554 | } 555 | this.#lastEntry.next = newEntry; 556 | } else { 557 | // Programmatically created key or the result of unconstrained parsing. 558 | // Then we need to test and sort (always produce deterministic CBOR). 559 | let precedingEntry = null; 560 | let diff = 0; 561 | for (let entry = this.#root; entry; entry = entry.next) { 562 | diff = entry.compare(newEntry.encodedKey); 563 | if (diff == 0) { 564 | throw Error("Duplicate: " + key); 565 | } 566 | if (diff > 0) { 567 | // New key is (lexicographically) smaller than current entry. 568 | if (precedingEntry == null) { 569 | // New key is smaller than root. New key becomes root. 570 | newEntry.next = this.#root; 571 | this.#root = newEntry; 572 | } else { 573 | // New key is smaller than an entry above root. Insert before current entry. 574 | newEntry.next = entry; 575 | precedingEntry.next = newEntry; 576 | } 577 | // Done, break out of the loop. 578 | break; 579 | } 580 | // No luck in this round, continue searching. 581 | precedingEntry = entry; 582 | } 583 | // Biggest key so far, insert it at the end. 584 | if (diff < 0) { 585 | precedingEntry.next = newEntry; 586 | } 587 | } 588 | } else { 589 | // First key, take it as is. 590 | this.#root = newEntry; 591 | } 592 | this.#lastEntry = newEntry; 593 | this.#numberOfEntries++; 594 | return this; 595 | } 596 | 597 | #getKey = function(key) { 598 | return CBOR.#cborArguentCheck(key); 599 | } 600 | 601 | #missingKey = function(key) { 602 | throw Error("Missing key: " + key); 603 | } 604 | 605 | #lookup(key, mustExist) { 606 | let encodedKey = this.#getKey(key).encode(); 607 | for (let entry = this.#root; entry; entry = entry.next) { 608 | if (entry.compare(encodedKey) == 0) { 609 | return entry; 610 | } 611 | } 612 | if (mustExist) { 613 | this.#missingKey(key); 614 | } 615 | return null; 616 | } 617 | 618 | get = function(key) { 619 | return this.#lookup(key, true).value; 620 | } 621 | 622 | getConditionally = function(key, defaultValue) { 623 | let entry = this.#lookup(key, false); 624 | // Note: defaultValue my be 'null' 625 | defaultValue = defaultValue ? CBOR.#cborArguentCheck(defaultValue) : null; 626 | return entry ? entry.value : defaultValue; 627 | } 628 | 629 | getKeys = function() { 630 | let keys = []; 631 | for (let entry = this.#root; entry; entry = entry.next) { 632 | keys.push(entry.key); 633 | } 634 | return keys; 635 | } 636 | 637 | remove = function(key) { 638 | let encodedKey = this.#getKey(key).encode(); 639 | let precedingEntry = null; 640 | for (let entry = this.#root; entry; entry = entry.next) { 641 | if (entry.compare(encodedKey) == 0) { 642 | if (precedingEntry == null) { 643 | // Remove root key. It may be alone. 644 | this.#root = entry.next; 645 | } else { 646 | // Remove key somewhere above root. 647 | precedingEntry.next = entry.next; 648 | } 649 | this.#numberOfEntries--; 650 | return entry.value; 651 | } 652 | precedingEntry = entry; 653 | } 654 | this.#missingKey(key); 655 | } 656 | 657 | size = function() { 658 | return this.#numberOfEntries; 659 | } 660 | 661 | containsKey = function(key) { 662 | return this.#lookup(key, false) != null; 663 | } 664 | 665 | encode = function() { 666 | console.log("nr=" + this.#numberOfEntries); 667 | let encoded = CBOR.#encodeTagAndN(CBOR.#MT_MAP, this.#numberOfEntries); 668 | for (let entry = this.#root; entry; entry = entry.next) { 669 | encoded = CBOR.addArrays(encoded, 670 | CBOR.addArrays(entry.key.encode(), entry.value.encode())); 671 | } 672 | return encoded; 673 | } 674 | 675 | toString = function(cborPrinter) { 676 | if (cborPrinter == undefined) { 677 | cborPrinter = new CBOR.#Printer(); 678 | } 679 | let notFirst = false; 680 | let buffer = cborPrinter.beginMap(); 681 | for (let entry = this.#root; entry; entry = entry.next) { 682 | if (notFirst) { 683 | buffer += ','; 684 | } 685 | notFirst = true; 686 | buffer += cborPrinter.newlineAndIndent(); 687 | buffer += entry.key.toString(cborPrinter) + ': ' + entry.value.toString(cborPrinter); 688 | } 689 | return buffer + cborPrinter.endMap(notFirst); 690 | } 691 | 692 | _get = function() { 693 | return this; 694 | } 695 | } 696 | 697 | /////////////////////////// 698 | // CBOR.Tag // 699 | /////////////////////////// 700 | 701 | static Tag = class extends CBOR.#CBORObject { 702 | 703 | static RESERVED_TAG_COTX = 1010n; 704 | 705 | #tagNumber; 706 | #object; 707 | 708 | constructor(tagNumber, object) { 709 | super(); 710 | if (typeof tagNumber != 'bigint') { 711 | tagNumber = BigInt(CBOR.#intCheck(tagNumber)); 712 | } 713 | if (tagNumber < 0n || tagNumber >= 0x10000000000000000n) { 714 | throw Error("Tag value is out of range"); 715 | } 716 | this.#tagNumber = tagNumber; 717 | this.#object = CBOR.#cborArguentCheck(object); 718 | } 719 | 720 | encode = function() { 721 | return CBOR.addArrays(CBOR.#finishBigIntAndTag(CBOR.#MT_TAG, this.#tagNumber), 722 | this.#object.encode()); 723 | } 724 | 725 | toString = function(cborPrinter) { 726 | return this.#tagNumber.toString() + '(' + this.#object.toString(cborPrinter) + ')'; 727 | } 728 | 729 | getTagNumber = function() { 730 | return this.#tagNumber; 731 | } 732 | 733 | getTagObject = function() { 734 | return this.#object; 735 | } 736 | 737 | _get = function() { 738 | return this; 739 | } 740 | } 741 | 742 | /////////////////////////// 743 | // Proxy // 744 | /////////////////////////// 745 | 746 | // The Proxy concept enables checks for invocation by "new" and number of arguments. 747 | static #handler = class { 748 | 749 | constructor(numberOfArguments) { 750 | this.numberOfArguments = numberOfArguments; 751 | } 752 | 753 | apply(target, thisArg, argumentsList) { 754 | if (argumentsList.length != this.numberOfArguments) { 755 | throw Error("CBOR." + target.name + " expects " + 756 | (this.numberOfArguments ? this.numberOfArguments.toString() : "no") + " arguments"); 757 | } 758 | return new target(...argumentsList); 759 | } 760 | 761 | construct(target, args) { 762 | throw Error("CBOR." + target.name + " does not permit \"new\""); 763 | } 764 | } 765 | 766 | static Int = new Proxy(CBOR.Int, new CBOR.#handler(1)); 767 | static BigInt = new Proxy(CBOR.BigInt, new CBOR.#handler(1)); 768 | static Float = new Proxy(CBOR.Float, new CBOR.#handler(1)); 769 | static String = new Proxy(CBOR.String, new CBOR.#handler(1)); 770 | static Bytes = new Proxy(CBOR.Bytes, new CBOR.#handler(1)); 771 | static Bool = new Proxy(CBOR.Bool, new CBOR.#handler(1)); 772 | static Null = new Proxy(CBOR.Null, new CBOR.#handler(0)); 773 | static Array = new Proxy(CBOR.Array, new CBOR.#handler(0)); 774 | static Map = new Proxy(CBOR.Map, new CBOR.#handler(0)); 775 | static Tag = new Proxy(CBOR.Tag, new CBOR.#handler(2)); 776 | 777 | 778 | /////////////////////////// 779 | // Decoder Core // 780 | /////////////////////////// 781 | 782 | static #_decoder = class { 783 | 784 | constructor(cbor, 785 | sequenceFlag, 786 | acceptNonDeterministic, 787 | constrainedKeys) { 788 | this.cbor = CBOR.#bytesCheck(cbor); 789 | this.counter = 0; 790 | this.sequenceFlag = sequenceFlag; 791 | this.deterministicMode = !acceptNonDeterministic; 792 | this.constrainedKeys = constrainedKeys; 793 | } 794 | 795 | readByte = function() { 796 | if (this.counter >= this.cbor.length) { 797 | if (this.sequenceFlag && this.atFirstByte) { 798 | return CBOR.#MT_NULL; 799 | } 800 | throw Error("Reading past end of buffer"); 801 | } 802 | this.atFirstByte = false; 803 | return this.cbor[this.counter++]; 804 | } 805 | 806 | readBytes = function (length) { 807 | let result = new Uint8Array(length); 808 | let q = -1; 809 | while (++q < length) { 810 | result[q] = this.readByte(); 811 | } 812 | return result; 813 | } 814 | 815 | unsupportedTag = function(tag) { 816 | throw Error("Unsupported tag: " + CBOR.#twoHex(tag)); 817 | } 818 | 819 | rangeLimitedBigInt = function(value) { 820 | if (value > 0xffffffffn) { 821 | throw Error("Length limited to 0xffffffff"); 822 | } 823 | return Number(value); 824 | } 825 | 826 | compareAndReturn = function(decoded, f64) { 827 | let cborFloat = CBOR.Float(f64); 828 | if (cborFloat._compare(decoded)) { 829 | console.log("FAIL=" + f64); 830 | throw Error("Did not work: " + f64); 831 | } 832 | return cborFloat; 833 | } 834 | 835 | shiftLeft = function(value) { 836 | let v = 1; 837 | let factor = 2; 838 | if (value < 0) { 839 | value = -value; 840 | factor = 0.5; 841 | } 842 | while (value--) { 843 | v *= factor; 844 | } 845 | console.log("MULT=" + v); 846 | return v; 847 | } 848 | 849 | recreateF64AndReturn = function(numberOfBytes, 850 | specialNumbers, 851 | significandMsbP1, 852 | offset, 853 | divisor) { 854 | let decoded = this.readBytes(numberOfBytes); 855 | let sign = false; 856 | if (decoded[0] & 0x80) { 857 | decoded[0] &= 0x7f; 858 | sign = true; 859 | } 860 | let float = 0n; 861 | for (let i = 0; i < decoded.length; i++) { 862 | float *= 256n; 863 | float += BigInt(decoded[i]); 864 | } 865 | let f64 = 0.0; 866 | while (true) { 867 | // The two cases of zero. 868 | if (!float) break; 869 | // The three cases of numbers that have no/little use. 870 | if ((float & specialNumbers) == specialNumbers) { 871 | f64 = (float == specialNumbers) ? Number.POSITIVE_INFINITY : Number.NaN; 872 | break; 873 | } 874 | // A genuine number 875 | let exponent = float & specialNumbers; 876 | console.log("exponent-1=" + exponent); 877 | let f64bin = float - exponent; 878 | console.log("calculated-1=" + f64bin); 879 | exponent /= significandMsbP1; 880 | console.log("exponent-2=" + exponent); 881 | if (exponent) { 882 | // Normal representation, add implicit "1.". 883 | f64bin += significandMsbP1; 884 | exponent--; 885 | console.log("calculated-2=" + f64bin); 886 | } 887 | if (exponent -= offset) { 888 | if (exponent < 0n) { 889 | f64bin >>= -exponent; 890 | } else { 891 | f64bin <<= exponent; 892 | } 893 | } 894 | console.log("calculated-3=" + f64bin); 895 | let array = []; 896 | while (f64bin) { 897 | array.push(Number(f64bin & 255n)); 898 | f64bin >>= 8n; 899 | } 900 | array = array.reverse(); 901 | for (let q = 0; q < array.length; q++) { 902 | f64 *= 256; 903 | f64 += array[q]; 904 | } 905 | f64 /= divisor; 906 | console.log("calculated-4=" + f64); 907 | break; 908 | } 909 | if (sign) { 910 | f64 = -f64; 911 | decoded[0] |= 0x80; 912 | } 913 | return this.compareAndReturn(decoded, f64); 914 | } 915 | 916 | getObject = function() { 917 | let tag = this.readByte(); 918 | 919 | // Begin with CBOR types that are uniquely defined by the tag byte. 920 | switch (tag) { 921 | case CBOR.#MT_BIG_NEGATIVE: 922 | case CBOR.#MT_BIG_UNSIGNED: 923 | let byteArray = this.getObject().getBytes(); 924 | if ((byteArray.length == 0 || byteArray[0] == 0 || byteArray.length <= 8) && 925 | this.deterministicMode) { 926 | throw Error("Non-deterministic big integer encoding"); 927 | } 928 | let value = 0n; 929 | byteArray.forEach(byte => { 930 | value <<= 8n; 931 | value += BigInt(byte); 932 | }); 933 | if (tag == CBOR.#MT_BIG_NEGATIVE) { 934 | value = ~value; 935 | } 936 | return CBOR.BigInt(value); 937 | 938 | case CBOR.#MT_FLOAT16: 939 | return this.recreateF64AndReturn(2, 0x7c00n, 0x400n, 15n, 0x200); 940 | 941 | case CBOR.#MT_FLOAT32: 942 | return this.recreateF64AndReturn(4, 0x7f800000n, 0x800000n, 127n, 0x400000); 943 | 944 | case CBOR.#MT_FLOAT64: 945 | let f64bytes = this.readBytes(8); 946 | const f64buffer = new ArrayBuffer(8); 947 | new Uint8Array(f64buffer).set(f64bytes); 948 | return this.compareAndReturn(f64bytes, new DataView(f64buffer).getFloat64(0, false)); 949 | 950 | case CBOR.#MT_NULL: 951 | return CBOR.Null(); 952 | 953 | case CBOR.#MT_TRUE: 954 | case CBOR.#MT_FALSE: 955 | return CBOR.Bool(tag == CBOR.#MT_TRUE); 956 | } 957 | // Then decode CBOR types that blend length of data in the tag byte. 958 | let n = tag & 0x1f; 959 | let bigN = BigInt(n); 960 | if (n > 27) { 961 | this.unsupportedTag(tag); 962 | } 963 | if (n > 23) { 964 | // For 1, 2, 4, and 8 byte N. 965 | let q = 1 << (n - 24); 966 | let mask = 0xffffffffn << BigInt((q >> 1) * 8); 967 | console.log('mask=' + mask.toString(16)); 968 | bigN = 0n; 969 | while (--q >= 0) { 970 | bigN <<= 8n; 971 | bigN += BigInt(this.readByte()); 972 | } 973 | console.log("bigN=" + bigN); 974 | // If the upper half (for 2, 4, 8 byte N) of N or a single byte 975 | // N is zero, a shorter variant should have been used. 976 | // In addition, N must be > 23. 977 | if ((bigN < 24n || !(mask & bigN)) && this.deterministicMode) { 978 | throw Error("Non-deterministic N encoding for tag: 0x" + CBOR.#twoHex(tag)); 979 | } 980 | } 981 | console.log("N=" + bigN + " " + (typeof BigN == 'bigint')); 982 | console.log(bigN); 983 | // N successfully decoded, now switch on major type (upper three bits). 984 | switch (tag & 0xe0) { 985 | 986 | case CBOR.#MT_TAG: 987 | let tagData = this.getObject(); 988 | if (bigN == CBOR.Tag.RESERVED_TAG_COTX) { 989 | if (!tagData instanceof CBOR.Array || tagData.size() != 2 || 990 | !tagData.get(0) instanceof CBOR.String) { 991 | throw Error("Tag syntax " + CBOR.Tag.RESERVED_TAG_COTX + 992 | "([\"string\", CBOR object]) expected"); 993 | } 994 | } 995 | return CBOR.Tag(bigN, tagData); 996 | 997 | case CBOR.#MT_UNSIGNED: 998 | if (bigN > BigInt(Number.MAX_SAFE_INTEGER)) { 999 | return CBOR.BigInt(bigN); 1000 | } 1001 | return CBOR.Int(Number(bigN)); 1002 | 1003 | case CBOR.#MT_NEGATIVE: 1004 | bigN = ~bigN; 1005 | if (bigN < BigInt(-Number.MAX_SAFE_INTEGER)) { 1006 | return CBOR.BigInt(bigN); 1007 | } 1008 | return CBOR.Int(Number(bigN)); 1009 | 1010 | case CBOR.#MT_BYTES: 1011 | return CBOR.Bytes(this.readBytes(this.rangeLimitedBigInt(bigN))); 1012 | 1013 | case CBOR.#MT_STRING: 1014 | return CBOR.String(new TextDecoder('utf-8', {fatal: true}).decode( 1015 | this.readBytes(this.rangeLimitedBigInt(bigN)))); 1016 | 1017 | case CBOR.#MT_ARRAY: 1018 | let cborArray = CBOR.Array(); 1019 | for (let q = this.rangeLimitedBigInt(bigN); --q >= 0;) { 1020 | cborArray.add(this.getObject()); 1021 | } 1022 | return cborArray; 1023 | 1024 | case CBOR.#MT_MAP: 1025 | let cborMap = CBOR.Map(); 1026 | cborMap._deterministicMode = this.deterministicMode; 1027 | cborMap._constrainedKeys = this.constrainedKeys; 1028 | for (let q = this.rangeLimitedBigInt(bigN); --q >= 0;) { 1029 | cborMap.set(this.getObject(), this.getObject()); 1030 | } 1031 | // Programmatically added elements sort automatically. 1032 | cborMap._deterministicMode = false; 1033 | return cborMap; 1034 | 1035 | default: 1036 | this.unsupportedTag(tag); 1037 | } 1038 | } 1039 | } 1040 | 1041 | static #getObject = function(decoder) { 1042 | decoder.atFirstByte = true; 1043 | let object = decoder.getObject(); 1044 | if (decoder.sequenceFlag) { 1045 | if (decoder.atFirstByte) { 1046 | return null; 1047 | } 1048 | } else if (decoder.counter < decoder.cbor.length) { 1049 | throw Error("Unexpected data encountered after CBOR object"); 1050 | } 1051 | return object; 1052 | } 1053 | 1054 | /////////////////////////// 1055 | // CBOR.decode() // 1056 | /////////////////////////// 1057 | 1058 | static decode = function(cbor) { 1059 | let decoder = new CBOR.#_decoder(cbor, false, false, false); 1060 | return CBOR.#getObject(decoder); 1061 | } 1062 | 1063 | /////////////////////////// 1064 | // CBOR.initExtended() // 1065 | /////////////////////////// 1066 | 1067 | static initExtended = function(cbor, sequenceFlag, acceptNonDeterministic, constrainedKeys) { 1068 | return new CBOR.#_decoder(cbor, 1069 | sequenceFlag, 1070 | acceptNonDeterministic, 1071 | constrainedKeys); 1072 | } 1073 | 1074 | /////////////////////////// 1075 | // CBOR.decodeExtended() // 1076 | /////////////////////////// 1077 | 1078 | static decodeExtended = function(decoder) { 1079 | return CBOR.#getObject(decoder); 1080 | } 1081 | 1082 | //================================// 1083 | // Internal Support Methods // 1084 | //================================// 1085 | 1086 | static #encodeTagAndN = function(majorType, n) { 1087 | let modifier = n; 1088 | let length = 0; 1089 | if (n > 23) { 1090 | modifier = 24; 1091 | length = 1; 1092 | let nextRange = 0x100; 1093 | while (length < 8 && n >= nextRange) { 1094 | modifier++; 1095 | length <<= 1; 1096 | // The last multiplication will not be an integer but "length < 8" handles this. 1097 | nextRange *= nextRange; 1098 | } 1099 | } 1100 | let encoded = new Uint8Array(length + 1); 1101 | encoded[0] = majorType | modifier; 1102 | while (length > 0) { 1103 | encoded[length--] = n; 1104 | n /= 256; 1105 | } 1106 | return encoded; 1107 | } 1108 | 1109 | static #bytesCheck = function(byteArray) { 1110 | if (byteArray instanceof Uint8Array) { 1111 | return byteArray; 1112 | } 1113 | throw Error("Argument is not an 'Uint8Array'"); 1114 | } 1115 | 1116 | static #typeCheck = function(object, type) { 1117 | if (typeof object != type) { 1118 | throw Error("Argument is not a '" + type + "'"); 1119 | } 1120 | return object; 1121 | } 1122 | 1123 | static #intCheck = function(value) { 1124 | CBOR.#typeCheck(value, 'number'); 1125 | if (!Number.isSafeInteger(value)) { 1126 | throw Error(Number.isInteger(value) ? 1127 | "Argument is outside of Number.MAX_SAFE_INTEGER" : "Argument is not an integer"); 1128 | } 1129 | return value; 1130 | } 1131 | 1132 | static #finishBigIntAndTag = function(tag, value) { 1133 | // Convert BigInt to Uint8Array (but with a twist). 1134 | let array = []; 1135 | do { 1136 | array.push(Number(value & 255n)); 1137 | value >>= 8n; 1138 | } while (value); 1139 | let length = array.length; 1140 | // Prepare for "Int" encoding (1, 2, 4, 8). Only 3, 5, 6, and 7 need an action. 1141 | while (length < 8 && length > 2 && length != 4) { 1142 | array.push(0); 1143 | length++; 1144 | } 1145 | let byteArray = new Uint8Array(array.reverse()); 1146 | // Does this number qualify as a "BigInt"? 1147 | if (length <= 8) { 1148 | // Apparently not, encode it as "Int". 1149 | if (length == 1 && byteArray[0] < 24) { 1150 | return new Uint8Array([tag | byteArray[0]]); 1151 | } 1152 | let modifier = 24; 1153 | while (length >>= 1) { 1154 | modifier++; 1155 | } 1156 | return CBOR.addArrays(new Uint8Array([tag | modifier]), byteArray); 1157 | } 1158 | // True "BigInt". 1159 | return CBOR.addArrays(new Uint8Array([tag == CBOR.#MT_NEGATIVE ? 1160 | CBOR.#MT_BIG_NEGATIVE : CBOR.#MT_BIG_UNSIGNED]), 1161 | CBOR.Bytes(byteArray).encode()); 1162 | } 1163 | 1164 | static #Printer = class { 1165 | indentationLevel = 0; 1166 | 1167 | beginMap = function() { 1168 | this.indentationLevel++; 1169 | return '{'; 1170 | } 1171 | 1172 | newlineAndIndent = function() { 1173 | let buffer = '\n'; 1174 | for (let i = 0; i < this.indentationLevel; i++) { 1175 | buffer += ' '; 1176 | } 1177 | return buffer; 1178 | } 1179 | 1180 | endMap = function(notEmpty) { 1181 | this.indentationLevel--; 1182 | return notEmpty ? this.newlineAndIndent() + '}' : '}'; 1183 | } 1184 | } 1185 | 1186 | static #int16ToByteArray = function(int16) { 1187 | return new Uint8Array([int16 / 256, int16 % 256]); 1188 | } 1189 | 1190 | static #f64ToByteArray = function(value) { 1191 | const buffer = new ArrayBuffer(8); 1192 | new DataView(buffer).setFloat64(0, value, false); 1193 | return [].slice.call(new Uint8Array(buffer)) 1194 | } 1195 | 1196 | static #oneHex = function(digit) { 1197 | return String.fromCharCode(digit < 10 ? (0x30 + digit) : (0x57 + digit)); 1198 | } 1199 | 1200 | static #twoHex = function(byte) { 1201 | return CBOR.#oneHex(byte / 16) + CBOR.#oneHex(byte % 16); 1202 | } 1203 | 1204 | static #cborArguentCheck = function(object) { 1205 | if (object instanceof CBOR.#CBORObject) { 1206 | return object; 1207 | } 1208 | throw Error(object ? "Argument is not a CBOR object: " + object.constructor.name : "'null'"); 1209 | } 1210 | 1211 | static #decodeOneHex(charCode) { 1212 | if (charCode >= 0x30 && charCode <= 0x39) return charCode - 0x30; 1213 | if (charCode >= 0x61 && charCode <= 0x66) return charCode - 0x57; 1214 | if (charCode >= 0x41 && charCode <= 0x46) return charCode - 0x37; 1215 | throw Error("Bad hex character: " + String.fromCharCode(charCode)); 1216 | } 1217 | 1218 | //================================// 1219 | // Public Support Methods // 1220 | //================================// 1221 | 1222 | static addArrays = function(a, b) { 1223 | let result = new Uint8Array(a.length + b.length); 1224 | let q = 0; 1225 | while (q < a.length) { 1226 | result[q] = a[q++]; 1227 | } 1228 | for (let i = 0; i < b.length; i++) { 1229 | result[q + i] = b[i]; 1230 | } 1231 | return result; 1232 | } 1233 | 1234 | static array 1235 | 1236 | static compareArrays = function(a, b) { 1237 | let minIndex = Math.min(a.length, b.length); 1238 | for (let i = 0; i < minIndex; i++) { 1239 | let diff = a[i] - b[i]; 1240 | if (diff != 0) { 1241 | return diff; 1242 | } 1243 | } 1244 | return a.length - b.length; 1245 | } 1246 | 1247 | static toHex = function (byteArray) { 1248 | let result = ''; 1249 | for (let i = 0; i < byteArray.length; i++) { 1250 | result += CBOR.#twoHex(byteArray[i]); 1251 | } 1252 | return result; 1253 | } 1254 | 1255 | static fromHex = function (hexString) { 1256 | let length = hexString.length; 1257 | if (length & 1) { 1258 | throw Error("Uneven number of characters in hex string"); 1259 | } 1260 | let result = new Uint8Array(length >> 1); 1261 | for (let q = 0; q < length; q += 2) { 1262 | result[q >> 1] = (CBOR.#decodeOneHex(hexString.charCodeAt(q)) << 4) + 1263 | CBOR.#decodeOneHex(hexString.charCodeAt(q + 1)); 1264 | } 1265 | return result; 1266 | } 1267 | 1268 | static toBase64Url = function(byteArray) { 1269 | return btoa(String.fromCharCode.apply(null, new Uint8Array(byteArray))) 1270 | .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 1271 | } 1272 | 1273 | static fromBase64Url = function(base64) { 1274 | if (!base64.includes('=')) { 1275 | base64 = base64 + '===='.substring(0, (4 - (base64.length % 4)) % 4); 1276 | } 1277 | return Uint8Array.from(atob(base64.replace(/-/g, '+').replace(/_/g, '/')), 1278 | c => c.charCodeAt(0)); 1279 | } 1280 | 1281 | } 1282 | 1283 | module.exports = CBOR; 1284 | -------------------------------------------------------------------------------- /src/cbor-js-api.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////// 2 | // // 3 | // CBOR JavaScript API // 4 | // // 5 | // Defines a single global object CBOR to (in some way) mimic the JSON object. // 6 | // Determinisic encoding aligned with Appendix A and 4.2.2 Rule 2 of RFC 8949. // 7 | // Author: Anders Rundgren (https://github.com/cyberphone) // 8 | ///////////////////////////////////////////////////////////////////////////////// 9 | 10 | 'use strict'; 11 | 12 | class CBOR { 13 | 14 | // Super class for all CBOR types. 15 | static #CBORObject = class { 16 | 17 | constructor() {} 18 | 19 | getInt = function() { 20 | if (this instanceof CBOR.BigInt) { 21 | // During decoding, integers outside of Number.MAX_SAFE_INTEGER 22 | // automatically get "BigInt" representation. 23 | throw RangeError("Integer is outside of Number.MAX_SAFE_INTEGER, use getBigInt()"); 24 | } 25 | return this.#checkTypeAndGetValue(CBOR.Int); 26 | } 27 | 28 | getString = function() { 29 | return this.#checkTypeAndGetValue(CBOR.String); 30 | } 31 | 32 | getBytes = function() { 33 | return this.#checkTypeAndGetValue(CBOR.Bytes); 34 | } 35 | 36 | getFloat = function() { 37 | return this.#checkTypeAndGetValue(CBOR.Float); 38 | } 39 | 40 | getBool = function() { 41 | return this.#checkTypeAndGetValue(CBOR.Bool); 42 | } 43 | 44 | getNull = function() { 45 | return this instanceof CBOR.Null; 46 | } 47 | 48 | getBigInt = function() { 49 | if (this instanceof CBOR.Int) { 50 | return BigInt(this._get()); 51 | } 52 | return this.#checkTypeAndGetValue(CBOR.BigInt); 53 | } 54 | 55 | getArray = function() { 56 | return this.#checkTypeAndGetValue(CBOR.Array); 57 | } 58 | 59 | getMap = function() { 60 | return this.#checkTypeAndGetValue(CBOR.Map); 61 | } 62 | 63 | getTag = function() { 64 | return this.#checkTypeAndGetValue(CBOR.Tag); 65 | } 66 | 67 | equals = function(object) { 68 | if (object && object instanceof CBOR.#CBORObject) { 69 | return CBOR.compareArrays(this.encode(), object.encode()) == 0; 70 | } 71 | return false; 72 | } 73 | 74 | clone = function() { 75 | return CBOR.decode(this.encode()); 76 | } 77 | 78 | // Overridden by CBOR.Int and CBOR.String 79 | constrainedKeyType = function() { 80 | return true; 81 | } 82 | 83 | #checkTypeAndGetValue = function(className) { 84 | if (!(this instanceof className)) { 85 | throw TypeError("Invalid method call for object: CBOR." + this.constructor.name); 86 | } 87 | return this._get(); 88 | } 89 | } 90 | 91 | static #MT_UNSIGNED = 0x00; 92 | static #MT_NEGATIVE = 0x20; 93 | static #MT_BYTES = 0x40; 94 | static #MT_STRING = 0x60; 95 | static #MT_ARRAY = 0x80; 96 | static #MT_MAP = 0xa0; 97 | static #MT_TAG = 0xc0; 98 | static #MT_BIG_UNSIGNED = 0xc2; 99 | static #MT_BIG_NEGATIVE = 0xc3; 100 | static #MT_FALSE = 0xf4; 101 | static #MT_TRUE = 0xf5; 102 | static #MT_NULL = 0xf6; 103 | static #MT_FLOAT16 = 0xf9; 104 | static #MT_FLOAT32 = 0xfa; 105 | static #MT_FLOAT64 = 0xfb; 106 | 107 | static #ESCAPE_CHARACTERS = [ 108 | // 0 1 2 3 4 5 6 7 8 9 A B C D E F 109 | 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 'b', 't', 'n', 1 , 'f', 'r', 1 , 1 , 110 | 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 111 | 0 , 0 , '"', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 112 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 113 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 114 | 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , '\\']; 115 | 116 | constructor() { 117 | throw Error("CBOR cannot be instantiated"); 118 | } 119 | 120 | /////////////////////////// 121 | // CBOR.Int // 122 | /////////////////////////// 123 | 124 | static Int = class extends CBOR.#CBORObject { 125 | 126 | #value; 127 | 128 | // Integers with a magnitude above 2^53 - 1, must use CBOR.BigInt. 129 | constructor(value) { 130 | super(); 131 | this.#value = CBOR.#intCheck(value); 132 | } 133 | 134 | encode = function() { 135 | let tag; 136 | let n = this.#value; 137 | if (n < 0) { 138 | tag = CBOR.#MT_NEGATIVE; 139 | n = -n - 1; 140 | } else { 141 | tag = CBOR.#MT_UNSIGNED; 142 | } 143 | return CBOR.#encodeTagAndN(tag, n); 144 | } 145 | 146 | toString = function() { 147 | return this.#value.toString(); 148 | } 149 | 150 | constrainedKeyType = function() { 151 | return false; 152 | } 153 | 154 | _get = function() { 155 | return this.#value; 156 | } 157 | } 158 | 159 | /////////////////////////// 160 | // CBOR.BigInt // 161 | /////////////////////////// 162 | 163 | static BigInt = class extends CBOR.#CBORObject { 164 | 165 | #value; 166 | 167 | // The CBOR.BigInt wrapper object implements the CBOR integer reduction algorithm. The 168 | // JavaScript "BigInt" object is used for maintaining lossless represention of large integers. 169 | constructor(value) { 170 | super(); 171 | this.#value = CBOR.#typeCheck(value, 'bigint'); 172 | } 173 | 174 | encode = function() { 175 | let tag; 176 | let value = this.#value 177 | if (value < 0) { 178 | tag = CBOR.#MT_NEGATIVE; 179 | value = ~value; 180 | } else { 181 | tag = CBOR.#MT_UNSIGNED; 182 | } 183 | return CBOR.#finishBigIntAndTag(tag, value); 184 | } 185 | 186 | toString = function() { 187 | return this.#value.toString(); 188 | } 189 | 190 | _get = function() { 191 | return this.#value; 192 | } 193 | } 194 | 195 | /////////////////////////// 196 | // CBOR.Float // 197 | /////////////////////////// 198 | 199 | static Float = class extends CBOR.#CBORObject { 200 | 201 | #value; 202 | #encoded; 203 | #tag; 204 | 205 | constructor(value) { 206 | super(); 207 | this.#value = CBOR.#typeCheck(value, 'number'); 208 | // Begin catching the F16 edge cases. 209 | this.#tag = CBOR.#MT_FLOAT16; 210 | if (Number.isNaN(value)) { 211 | this.#encoded = CBOR.#int16ToByteArray(0x7e00); 212 | } else if (!Number.isFinite(value)) { 213 | this.#encoded = CBOR.#int16ToByteArray(value < 0 ? 0xfc00 : 0x7c00); 214 | } else if (Math.abs(value) == 0) { 215 | this.#encoded = CBOR.#int16ToByteArray(Object.is(value,-0) ? 0x8000 : 0x0000); 216 | } else { 217 | // It is apparently a genuine (non-zero) number. 218 | // The following code depends on that Math.fround works as expected. 219 | let f32 = Math.fround(value); 220 | let u8; 221 | let f32exp; 222 | let f32signif; 223 | while (true) { // "goto" surely beats quirky loop/break/return/flag constructs... 224 | if (f32 == value) { 225 | // Nothing was lost during the conversion, F32 or F16 is on the menu. 226 | this.#tag = CBOR.#MT_FLOAT32; 227 | // However, JavaScript always defer to F64 for "Number". 228 | u8 = CBOR.#f64ToByteArray(value); 229 | f32exp = ((u8[0] & 0x7f) << 4) + ((u8[1] & 0xf0) >> 4) - 1023 + 127; 230 | f32signif = ((u8[1] & 0x0f) << 19) + (u8[2] << 11) + (u8[3] << 3) + (u8[4] >> 5) 231 | // Very small F32 numbers may require subnormal representation. 232 | if (f32exp <= 0) { 233 | // The implicit "1" becomes explicit using subnormal representation. 234 | f32signif += 1 << 23; 235 | // Always perform at least one turn. 236 | f32exp--; 237 | do { 238 | f32signif >>= 1; 239 | } while (++f32exp < 0); 240 | } 241 | // If it is a subnormal F32 or if F16 would lose precision, stick to F32. 242 | if (f32exp == 0 || f32signif & 0x1fff) { 243 | break; 244 | } 245 | // Arrange for F16. 246 | let f16exp = f32exp - 127 + 15; 247 | let f16signif = f32signif >> 13; 248 | // If too large for F16, stick to F32. 249 | if (f16exp > 30) { 250 | break; 251 | } 252 | // Finally, is value too small for F16? 253 | if (f16exp <= 0) { 254 | // The implicit "1" becomes explicit using subnormal representation. 255 | f16signif += 1 << 10; 256 | // Always perform at least one turn. 257 | f16exp--; 258 | do { 259 | // Losing bits is not an option. 260 | if ((f16signif & 1) != 0) { 261 | f16signif = 0; 262 | break; 263 | } 264 | f16signif >>= 1; 265 | } while (++f16exp < 0); 266 | // If too small for F16, stick to F32. 267 | if (f16signif == 0) { 268 | break; 269 | } 270 | } 271 | // A rarity, 16 bits turned out being sufficient for representing value. 272 | this.#tag = CBOR.#MT_FLOAT16; 273 | let f16bin = 274 | // Put sign bit in position. 275 | ((u8[0] & 0x80) << 8) + 276 | // Exponent. Put it in front of significand. 277 | (f16exp << 10) + 278 | // Significand. 279 | f16signif; 280 | this.#encoded = CBOR.#int16ToByteArray(f16bin); 281 | } else { 282 | // Converting to F32 returned a truncated result. 283 | // Full 64-bit representation is required. 284 | this.#tag = CBOR.#MT_FLOAT64; 285 | this.#encoded = CBOR.#f64ToByteArray(value); 286 | } 287 | // Common F16 and F64 return point. 288 | return; 289 | } 290 | // Broken loop: 32 bits are apparently needed for maintaining magnitude and precision. 291 | let f32bin = 292 | // Put sign bit in position. Why not << 24? JS shift doesn't work above 2^31... 293 | ((u8[0] & 0x80) * 0x1000000) + 294 | // Exponent. Put it in front of significand (<< 23). 295 | (f32exp * 0x800000) + 296 | // Significand. 297 | f32signif; 298 | this.#encoded = CBOR.addArrays(CBOR.#int16ToByteArray(f32bin / 0x10000), 299 | CBOR.#int16ToByteArray(f32bin % 0x10000)); 300 | } 301 | } 302 | 303 | encode = function() { 304 | return CBOR.addArrays(new Uint8Array([this.#tag]), this.#encoded); 305 | } 306 | 307 | toString = function() { 308 | let floatString = this.#value.toString(); 309 | // Diagnostic Notation support. 310 | if (floatString.indexOf('.') < 0) { 311 | let matches = floatString.match(/\-?\d+/g); 312 | if (matches) { 313 | floatString = matches[0] + '.0' + floatString.substring(matches[0].length); 314 | } 315 | } 316 | return floatString; 317 | } 318 | 319 | _compare = function(decoded) { 320 | return CBOR.compareArrays(this.#encoded, decoded); 321 | } 322 | 323 | _get = function() { 324 | return this.#value; 325 | } 326 | } 327 | 328 | /////////////////////////// 329 | // CBOR.String // 330 | /////////////////////////// 331 | 332 | static String = class extends CBOR.#CBORObject { 333 | 334 | #string; 335 | 336 | constructor(string) { 337 | super(); 338 | this.#string = CBOR.#typeCheck(string, 'string'); 339 | } 340 | 341 | encode = function() { 342 | let utf8 = new TextEncoder().encode(this.#string); 343 | return CBOR.addArrays(CBOR.#encodeTagAndN(CBOR.#MT_STRING, utf8.length), utf8); 344 | } 345 | 346 | toString = function() { 347 | let buffer = '"'; 348 | for (let q = 0; q < this.#string.length; q++) { 349 | let c = this.#string.charCodeAt(q); 350 | if (c <= 0x5c) { 351 | let escapedCharacter; 352 | if (escapedCharacter = CBOR.#ESCAPE_CHARACTERS[c]) { 353 | buffer += '\\'; 354 | if (escapedCharacter == 1) { 355 | buffer += 'u00' + CBOR.#twoHex(c); 356 | } else { 357 | buffer += escapedCharacter; 358 | } 359 | continue; 360 | } 361 | } 362 | buffer += String.fromCharCode(c); 363 | } 364 | return buffer + '"'; 365 | } 366 | 367 | constrainedKeyType = function() { 368 | return false; 369 | } 370 | 371 | _get = function() { 372 | return this.#string; 373 | } 374 | } 375 | 376 | /////////////////////////// 377 | // CBOR.Bytes // 378 | /////////////////////////// 379 | 380 | static Bytes = class extends CBOR.#CBORObject { 381 | 382 | #bytes; 383 | 384 | constructor(bytes) { 385 | super(); 386 | this.#bytes = CBOR.#bytesCheck(bytes); 387 | } 388 | 389 | encode = function() { 390 | return CBOR.addArrays(CBOR.#encodeTagAndN(CBOR.#MT_BYTES, this.#bytes.length), this.#bytes); 391 | } 392 | 393 | toString = function() { 394 | return "h'" + CBOR.toHex(this.#bytes) + "'"; 395 | } 396 | 397 | _get = function() { 398 | return this.#bytes; 399 | } 400 | } 401 | 402 | /////////////////////////// 403 | // CBOR.Bool // 404 | /////////////////////////// 405 | 406 | static Bool = class extends CBOR.#CBORObject { 407 | 408 | #bool; 409 | 410 | constructor(bool) { 411 | super(); 412 | this.#bool = CBOR.#typeCheck(bool, 'boolean'); 413 | } 414 | 415 | encode = function() { 416 | return new Uint8Array([this.#bool ? CBOR.#MT_TRUE : CBOR.#MT_FALSE]); 417 | } 418 | 419 | toString = function() { 420 | return this.#bool.toString(); 421 | } 422 | 423 | _get = function() { 424 | return this.#bool; 425 | } 426 | } 427 | 428 | /////////////////////////// 429 | // CBOR.Null // 430 | /////////////////////////// 431 | 432 | static Null = class extends CBOR.#CBORObject { 433 | 434 | encode = function() { 435 | return new Uint8Array([CBOR.#MT_NULL]); 436 | } 437 | 438 | toString = function() { 439 | return 'null'; 440 | } 441 | } 442 | 443 | /////////////////////////// 444 | // CBOR.Array // 445 | /////////////////////////// 446 | 447 | static Array = class extends CBOR.#CBORObject { 448 | 449 | #elements = []; 450 | 451 | add = function(element) { 452 | this.#elements.push(CBOR.#cborArguentCheck(element)); 453 | return this; 454 | } 455 | 456 | get = function(index) { 457 | index = CBOR.#intCheck(index); 458 | if (index < 0 || index >= this.#elements.length) { 459 | throw RangeError("Array index out of range: " + index); 460 | } 461 | return this.#elements[index]; 462 | } 463 | 464 | toArray = function() { 465 | let array = []; 466 | this.#elements.forEach(element => array.push(element)); 467 | return array; 468 | } 469 | 470 | encode = function() { 471 | let encoded = CBOR.#encodeTagAndN(CBOR.#MT_ARRAY, this.#elements.length); 472 | this.#elements.forEach(object => { 473 | encoded = CBOR.addArrays(encoded, object.encode()); 474 | }); 475 | return encoded; 476 | } 477 | 478 | toString = function(cborPrinter) { 479 | let buffer = '['; 480 | let notFirst = false; 481 | this.#elements.forEach(object => { 482 | if (notFirst) { 483 | buffer += ', '; 484 | } 485 | notFirst = true; 486 | buffer += object.toString(cborPrinter); 487 | }); 488 | return buffer + ']'; 489 | } 490 | 491 | size = function() { 492 | return this.#elements.length; 493 | } 494 | 495 | _get = function() { 496 | return this; 497 | } 498 | } 499 | 500 | /////////////////////////// 501 | // CBOR.Map // 502 | /////////////////////////// 503 | 504 | static Map = class extends CBOR.#CBORObject { 505 | 506 | #root; 507 | #lastEntry; 508 | #numberOfEntries = 0; 509 | _constrainedKeys = false; 510 | _deterministicMode = false; 511 | 512 | static Entry = class { 513 | 514 | constructor(key, value) { 515 | this.key = key; 516 | this.encodedKey = key.encode(); 517 | this.value = value; 518 | this.next = null; 519 | } 520 | 521 | compare = function(encodedKey) { 522 | return CBOR.compareArrays(this.encodedKey, encodedKey); 523 | } 524 | } 525 | 526 | set = function(key, value) { 527 | let newEntry = new CBOR.Map.Entry(this.#getKey(key), CBOR.#cborArguentCheck(value)); 528 | if (this._constrainedKeys && key.constrainedKeyType()) { 529 | throw Error("Constrained key option disallows: " + key.constructor.name); 530 | } 531 | if (this.#root) { 532 | // Second key etc. 533 | if (this._constrainedKeys && 534 | this.#lastEntry.key.constructor.name != key.constructor.name) { 535 | throw Error("Constrained key option disallows mixing types: " + key.constructor.name); 536 | } 537 | if (this._deterministicMode) { 538 | // Normal case for parsing. 539 | let diff = this.#lastEntry.compare(newEntry.encodedKey); 540 | if (diff >= 0) { 541 | throw Error((diff ? "Non-deterministic order: " : "Duplicate: ") + key); 542 | } 543 | this.#lastEntry.next = newEntry; 544 | } else { 545 | // Programmatically created key or the result of unconstrained parsing. 546 | // Then we need to test and sort (always produce deterministic CBOR). 547 | let precedingEntry = null; 548 | let diff = 0; 549 | for (let entry = this.#root; entry; entry = entry.next) { 550 | diff = entry.compare(newEntry.encodedKey); 551 | if (diff == 0) { 552 | throw Error("Duplicate: " + key); 553 | } 554 | if (diff > 0) { 555 | // New key is (lexicographically) smaller than current entry. 556 | if (precedingEntry == null) { 557 | // New key is smaller than root. New key becomes root. 558 | newEntry.next = this.#root; 559 | this.#root = newEntry; 560 | } else { 561 | // New key is smaller than an entry above root. Insert before current entry. 562 | newEntry.next = entry; 563 | precedingEntry.next = newEntry; 564 | } 565 | // Done, break out of the loop. 566 | break; 567 | } 568 | // No luck in this round, continue searching. 569 | precedingEntry = entry; 570 | } 571 | // Biggest key so far, insert it at the end. 572 | if (diff < 0) { 573 | precedingEntry.next = newEntry; 574 | } 575 | } 576 | } else { 577 | // First key, take it as is. 578 | this.#root = newEntry; 579 | } 580 | this.#lastEntry = newEntry; 581 | this.#numberOfEntries++; 582 | return this; 583 | } 584 | 585 | #getKey = function(key) { 586 | return CBOR.#cborArguentCheck(key); 587 | } 588 | 589 | #missingKey = function(key) { 590 | throw Error("Missing key: " + key); 591 | } 592 | 593 | #lookup(key, mustExist) { 594 | let encodedKey = this.#getKey(key).encode(); 595 | for (let entry = this.#root; entry; entry = entry.next) { 596 | if (entry.compare(encodedKey) == 0) { 597 | return entry; 598 | } 599 | } 600 | if (mustExist) { 601 | this.#missingKey(key); 602 | } 603 | return null; 604 | } 605 | 606 | get = function(key) { 607 | return this.#lookup(key, true).value; 608 | } 609 | 610 | getConditionally = function(key, defaultValue) { 611 | let entry = this.#lookup(key, false); 612 | // Note: defaultValue may be 'null' 613 | defaultValue = defaultValue ? CBOR.#cborArguentCheck(defaultValue) : null; 614 | return entry ? entry.value : defaultValue; 615 | } 616 | 617 | getKeys = function() { 618 | let keys = []; 619 | for (let entry = this.#root; entry; entry = entry.next) { 620 | keys.push(entry.key); 621 | } 622 | return keys; 623 | } 624 | 625 | remove = function(key) { 626 | let encodedKey = this.#getKey(key).encode(); 627 | let precedingEntry = null; 628 | for (let entry = this.#root; entry; entry = entry.next) { 629 | if (entry.compare(encodedKey) == 0) { 630 | if (precedingEntry == null) { 631 | // Remove root key. It may be alone. 632 | this.#root = entry.next; 633 | } else { 634 | // Remove key somewhere above root. 635 | precedingEntry.next = entry.next; 636 | } 637 | this.#numberOfEntries--; 638 | return entry.value; 639 | } 640 | precedingEntry = entry; 641 | } 642 | this.#missingKey(key); 643 | } 644 | 645 | size = function() { 646 | return this.#numberOfEntries; 647 | } 648 | 649 | containsKey = function(key) { 650 | return this.#lookup(key, false) != null; 651 | } 652 | 653 | encode = function() { 654 | let encoded = CBOR.#encodeTagAndN(CBOR.#MT_MAP, this.#numberOfEntries); 655 | for (let entry = this.#root; entry; entry = entry.next) { 656 | encoded = CBOR.addArrays(encoded, 657 | CBOR.addArrays(entry.key.encode(), entry.value.encode())); 658 | } 659 | return encoded; 660 | } 661 | 662 | toString = function(cborPrinter) { 663 | if (cborPrinter == undefined) { 664 | cborPrinter = new CBOR.#Printer(); 665 | } 666 | let notFirst = false; 667 | let buffer = cborPrinter.beginMap(); 668 | for (let entry = this.#root; entry; entry = entry.next) { 669 | if (notFirst) { 670 | buffer += ','; 671 | } 672 | notFirst = true; 673 | buffer += cborPrinter.newlineAndIndent(); 674 | buffer += entry.key.toString(cborPrinter) + ': ' + entry.value.toString(cborPrinter); 675 | } 676 | return buffer + cborPrinter.endMap(notFirst); 677 | } 678 | 679 | _get = function() { 680 | return this; 681 | } 682 | } 683 | 684 | /////////////////////////// 685 | // CBOR.Tag // 686 | /////////////////////////// 687 | 688 | static Tag = class extends CBOR.#CBORObject { 689 | 690 | static RESERVED_TAG_COTX = 1010n; 691 | 692 | #tagNumber; 693 | #object; 694 | 695 | constructor(tagNumber, object) { 696 | super(); 697 | if (typeof tagNumber != 'bigint') { 698 | tagNumber = BigInt(CBOR.#intCheck(tagNumber)); 699 | } 700 | if (tagNumber < 0n || tagNumber >= 0x10000000000000000n) { 701 | throw RangeError("Tag value is out of range"); 702 | } 703 | this.#tagNumber = tagNumber; 704 | this.#object = CBOR.#cborArguentCheck(object); 705 | } 706 | 707 | encode = function() { 708 | return CBOR.addArrays(CBOR.#finishBigIntAndTag(CBOR.#MT_TAG, this.#tagNumber), 709 | this.#object.encode()); 710 | } 711 | 712 | toString = function(cborPrinter) { 713 | return this.#tagNumber.toString() + '(' + this.#object.toString(cborPrinter) + ')'; 714 | } 715 | 716 | getTagNumber = function() { 717 | return this.#tagNumber; 718 | } 719 | 720 | getTagObject = function() { 721 | return this.#object; 722 | } 723 | 724 | _get = function() { 725 | return this; 726 | } 727 | } 728 | 729 | /////////////////////////// 730 | // Proxy // 731 | /////////////////////////// 732 | 733 | // The Proxy concept enables checks for invocation by "new" and number of arguments. 734 | static #handler = class { 735 | 736 | constructor(numberOfArguments) { 737 | this.numberOfArguments = numberOfArguments; 738 | } 739 | 740 | apply(target, thisArg, argumentsList) { 741 | if (argumentsList.length != this.numberOfArguments) { 742 | throw SyntaxError("CBOR." + target.name + " expects " + 743 | this.numberOfArguments + " argument(s)"); 744 | } 745 | return new target(...argumentsList); 746 | } 747 | 748 | construct(target, args) { 749 | throw SyntaxError("CBOR." + target.name + " does not permit \"new\""); 750 | } 751 | } 752 | 753 | static Int = new Proxy(CBOR.Int, new CBOR.#handler(1)); 754 | static BigInt = new Proxy(CBOR.BigInt, new CBOR.#handler(1)); 755 | static Float = new Proxy(CBOR.Float, new CBOR.#handler(1)); 756 | static String = new Proxy(CBOR.String, new CBOR.#handler(1)); 757 | static Bytes = new Proxy(CBOR.Bytes, new CBOR.#handler(1)); 758 | static Bool = new Proxy(CBOR.Bool, new CBOR.#handler(1)); 759 | static Null = new Proxy(CBOR.Null, new CBOR.#handler(0)); 760 | static Array = new Proxy(CBOR.Array, new CBOR.#handler(0)); 761 | static Map = new Proxy(CBOR.Map, new CBOR.#handler(0)); 762 | static Tag = new Proxy(CBOR.Tag, new CBOR.#handler(2)); 763 | 764 | 765 | /////////////////////////// 766 | // Decoder Core // 767 | /////////////////////////// 768 | 769 | static #_decoder = class { 770 | 771 | constructor(cbor, 772 | sequenceFlag, 773 | acceptNonDeterministic, 774 | constrainedKeys) { 775 | this.cbor = CBOR.#bytesCheck(cbor); 776 | this.counter = 0; 777 | this.sequenceFlag = sequenceFlag; 778 | this.deterministicMode = !acceptNonDeterministic; 779 | this.constrainedKeys = constrainedKeys; 780 | } 781 | 782 | readByte = function() { 783 | if (this.counter >= this.cbor.length) { 784 | if (this.sequenceFlag && this.atFirstByte) { 785 | return CBOR.#MT_NULL; 786 | } 787 | throw Error("Reading past end of buffer"); 788 | } 789 | this.atFirstByte = false; 790 | return this.cbor[this.counter++]; 791 | } 792 | 793 | readBytes = function (length) { 794 | let result = new Uint8Array(length); 795 | let q = -1; 796 | while (++q < length) { 797 | result[q] = this.readByte(); 798 | } 799 | return result; 800 | } 801 | 802 | unsupportedTag = function(tag) { 803 | throw Error("Unsupported tag: " + CBOR.#twoHex(tag)); 804 | } 805 | 806 | rangeLimitedBigInt = function(value) { 807 | if (value > 0xffffffffn) { 808 | throw RangeError("Length limited to 0xffffffff"); 809 | } 810 | return Number(value); 811 | } 812 | 813 | compareAndReturn = function(decoded, f64) { 814 | let cborFloat = CBOR.Float(f64); 815 | if (cborFloat._compare(decoded) && this.deterministicMode) { 816 | throw Error("Non-deterministic encoding of: " + f64); 817 | } 818 | return cborFloat; 819 | } 820 | 821 | recreateF64AndReturn = function(numberOfBytes, 822 | specialNumbers, 823 | significandMsbP1, 824 | divisor) { 825 | let decoded = this.readBytes(numberOfBytes); 826 | let sign = false; 827 | if (decoded[0] & 0x80) { 828 | decoded[0] &= 0x7f; 829 | sign = true; 830 | } 831 | let float = 0n; 832 | for (let i = 0; i < decoded.length; i++) { 833 | float *= 256n; 834 | float += BigInt(decoded[i]); 835 | } 836 | let f64 = 0.0; 837 | while (true) { 838 | // The two cases of zero. 839 | if (!float) break; 840 | // The three cases of numbers that have no/little use. 841 | if ((float & specialNumbers) == specialNumbers) { 842 | f64 = (float == specialNumbers) ? Number.POSITIVE_INFINITY : Number.NaN; 843 | break; 844 | } 845 | // A genuine number 846 | let exponent = float & specialNumbers; 847 | let f64bin = float - exponent; 848 | exponent /= significandMsbP1; 849 | if (exponent) { 850 | // Normal representation, add implicit "1.". 851 | f64bin += significandMsbP1; 852 | exponent--; 853 | } 854 | f64bin <<= exponent; 855 | let array = []; 856 | while (f64bin) { 857 | array.push(Number(f64bin & 255n)); 858 | f64bin >>= 8n; 859 | } 860 | array = array.reverse(); 861 | for (let q = 0; q < array.length; q++) { 862 | f64 *= 256; 863 | f64 += array[q]; 864 | } 865 | f64 /= divisor; 866 | break; 867 | } 868 | if (sign) { 869 | f64 = -f64; 870 | decoded[0] |= 0x80; 871 | } 872 | return this.compareAndReturn(decoded, f64); 873 | } 874 | 875 | getObject = function() { 876 | let tag = this.readByte(); 877 | 878 | // Begin with CBOR types that are uniquely defined by the tag byte. 879 | switch (tag) { 880 | case CBOR.#MT_BIG_NEGATIVE: 881 | case CBOR.#MT_BIG_UNSIGNED: 882 | let byteArray = this.getObject().getBytes(); 883 | if ((byteArray.length == 0 || byteArray[0] == 0 || byteArray.length <= 8) && 884 | this.deterministicMode) { 885 | throw Error("Non-deterministic big integer encoding"); 886 | } 887 | let value = 0n; 888 | byteArray.forEach(byte => { 889 | value <<= 8n; 890 | value += BigInt(byte); 891 | }); 892 | if (tag == CBOR.#MT_BIG_NEGATIVE) { 893 | value = ~value; 894 | } 895 | return CBOR.BigInt(value); 896 | 897 | case CBOR.#MT_FLOAT16: 898 | return this.recreateF64AndReturn(2, 0x7c00n, 0x400n, 0x1000000); 899 | 900 | case CBOR.#MT_FLOAT32: 901 | return this.recreateF64AndReturn(4, 0x7f800000n, 0x800000n, 902 | 0x20000000000000000000000000000000000000); 903 | 904 | case CBOR.#MT_FLOAT64: 905 | let f64bytes = this.readBytes(8); 906 | const f64buffer = new ArrayBuffer(8); 907 | new Uint8Array(f64buffer).set(f64bytes); 908 | return this.compareAndReturn(f64bytes, new DataView(f64buffer).getFloat64(0, false)); 909 | 910 | case CBOR.#MT_NULL: 911 | return CBOR.Null(); 912 | 913 | case CBOR.#MT_TRUE: 914 | case CBOR.#MT_FALSE: 915 | return CBOR.Bool(tag == CBOR.#MT_TRUE); 916 | } 917 | // Then decode CBOR types that blend length of data in the tag byte. 918 | let n = tag & 0x1f; 919 | let bigN = BigInt(n); 920 | if (n > 27) { 921 | this.unsupportedTag(tag); 922 | } 923 | if (n > 23) { 924 | // For 1, 2, 4, and 8 byte N. 925 | let q = 1 << (n - 24); 926 | let mask = 0xffffffffn << BigInt((q >> 1) * 8); 927 | bigN = 0n; 928 | while (--q >= 0) { 929 | bigN <<= 8n; 930 | bigN += BigInt(this.readByte()); 931 | } 932 | // If the upper half (for 2, 4, 8 byte N) of N or a single byte 933 | // N is zero, a shorter variant should have been used. 934 | // In addition, N must be > 23. 935 | if ((bigN < 24n || !(mask & bigN)) && this.deterministicMode) { 936 | throw Error("Non-deterministic N encoding for tag: 0x" + CBOR.#twoHex(tag)); 937 | } 938 | } 939 | // N successfully decoded, now switch on major type (upper three bits). 940 | switch (tag & 0xe0) { 941 | 942 | case CBOR.#MT_TAG: 943 | let tagData = this.getObject(); 944 | if (bigN == CBOR.Tag.RESERVED_TAG_COTX) { 945 | if (!tagData instanceof CBOR.Array || tagData.size() != 2 || 946 | !tagData.get(0) instanceof CBOR.String) { 947 | throw SyntaxError("Tag syntax " + CBOR.Tag.RESERVED_TAG_COTX + 948 | "([\"string\", CBOR object]) expected"); 949 | } 950 | } 951 | return CBOR.Tag(bigN, tagData); 952 | 953 | case CBOR.#MT_UNSIGNED: 954 | if (bigN > BigInt(Number.MAX_SAFE_INTEGER)) { 955 | return CBOR.BigInt(bigN); 956 | } 957 | return CBOR.Int(Number(bigN)); 958 | 959 | case CBOR.#MT_NEGATIVE: 960 | bigN = ~bigN; 961 | if (bigN < BigInt(-Number.MAX_SAFE_INTEGER)) { 962 | return CBOR.BigInt(bigN); 963 | } 964 | return CBOR.Int(Number(bigN)); 965 | 966 | case CBOR.#MT_BYTES: 967 | return CBOR.Bytes(this.readBytes(this.rangeLimitedBigInt(bigN))); 968 | 969 | case CBOR.#MT_STRING: 970 | return CBOR.String(new TextDecoder('utf-8', {fatal: true}).decode( 971 | this.readBytes(this.rangeLimitedBigInt(bigN)))); 972 | 973 | case CBOR.#MT_ARRAY: 974 | let cborArray = CBOR.Array(); 975 | for (let q = this.rangeLimitedBigInt(bigN); --q >= 0;) { 976 | cborArray.add(this.getObject()); 977 | } 978 | return cborArray; 979 | 980 | case CBOR.#MT_MAP: 981 | let cborMap = CBOR.Map(); 982 | cborMap._deterministicMode = this.deterministicMode; 983 | cborMap._constrainedKeys = this.constrainedKeys; 984 | for (let q = this.rangeLimitedBigInt(bigN); --q >= 0;) { 985 | cborMap.set(this.getObject(), this.getObject()); 986 | } 987 | // Programmatically added elements sort automatically. 988 | cborMap._deterministicMode = false; 989 | return cborMap; 990 | 991 | default: 992 | this.unsupportedTag(tag); 993 | } 994 | } 995 | } 996 | 997 | static #getObject = function(decoder) { 998 | decoder.atFirstByte = true; 999 | let object = decoder.getObject(); 1000 | if (decoder.sequenceFlag) { 1001 | if (decoder.atFirstByte) { 1002 | return null; 1003 | } 1004 | } else if (decoder.counter < decoder.cbor.length) { 1005 | throw Error("Unexpected data encountered after CBOR object"); 1006 | } 1007 | return object; 1008 | } 1009 | 1010 | /////////////////////////// 1011 | // CBOR.decode() // 1012 | /////////////////////////// 1013 | 1014 | static decode = function(cbor) { 1015 | let decoder = new CBOR.#_decoder(cbor, false, false, false); 1016 | return CBOR.#getObject(decoder); 1017 | } 1018 | 1019 | /////////////////////////// 1020 | // CBOR.initExtended() // 1021 | /////////////////////////// 1022 | 1023 | static initExtended = function(cbor, sequenceFlag, acceptNonDeterministic, constrainedKeys) { 1024 | return new CBOR.#_decoder(cbor, 1025 | sequenceFlag, 1026 | acceptNonDeterministic, 1027 | constrainedKeys); 1028 | } 1029 | 1030 | /////////////////////////// 1031 | // CBOR.decodeExtended() // 1032 | /////////////////////////// 1033 | 1034 | static decodeExtended = function(decoder) { 1035 | return CBOR.#getObject(decoder); 1036 | } 1037 | 1038 | //================================// 1039 | // Diagnostic Notation Support // 1040 | //================================// 1041 | 1042 | static #DiagnosticNotation = class { 1043 | 1044 | cborText; 1045 | index; 1046 | sequence; 1047 | 1048 | constructor(cborText, sequence) { 1049 | this.cborText = cborText; 1050 | this.sequence = sequence; 1051 | this.index = 0; 1052 | } 1053 | 1054 | reportError = function(error) { 1055 | // Unsurprisingly, error handling turned out to be the most complex part... 1056 | let start = this.index - 100; 1057 | if (start < 0) { 1058 | start = 0; 1059 | } 1060 | let linePos = 0; 1061 | while (start < this.index - 1) { 1062 | if (this.cborText[start++] == '\n') { 1063 | linePos = start; 1064 | } 1065 | } 1066 | let complete = ''; 1067 | if (this.index > 0 && this.cborText[this.index - 1] == '\n') { 1068 | this.index--; 1069 | } 1070 | let endLine = this.index; 1071 | while (endLine < this.cborText.length) { 1072 | if (this.cborText[endLine] == '\n') { 1073 | break; 1074 | } 1075 | endLine++; 1076 | } 1077 | for (let q = linePos; q < endLine; q++) { 1078 | complete += this.cborText[q]; 1079 | } 1080 | complete += '\n'; 1081 | for (let q = linePos; q < this.index; q++) { 1082 | complete += '-'; 1083 | } 1084 | let lineNumber = 1; 1085 | for (let q = 0; q < this.index - 1; q++) { 1086 | if (this.cborText[q] == '\n') { 1087 | lineNumber++; 1088 | } 1089 | } 1090 | throw SyntaxError(complete + "^\n\nError in line " + lineNumber + ". " + error); 1091 | } 1092 | 1093 | readToEOF = function() { 1094 | let cborObject = this.getObject(); 1095 | if (this.index < this.cborText.length) { 1096 | this.readChar(); 1097 | this.reportError("Unexpected data after token"); 1098 | } 1099 | return cborObject; 1100 | } 1101 | 1102 | readSequenceToEOF = function() { 1103 | let sequence = []; 1104 | while (true) { 1105 | sequence.push(this.getObject()); 1106 | if (this.index < this.cborText.length) { 1107 | this.scanFor(","); 1108 | } else { 1109 | return sequence; 1110 | } 1111 | } 1112 | } 1113 | 1114 | getObject = function() { 1115 | this.scanNonSignficantData(); 1116 | let cborObject = this.getRawObject(); 1117 | this.scanNonSignficantData(); 1118 | return cborObject; 1119 | } 1120 | 1121 | continueList = function(validStop) { 1122 | if (this.nextChar() == ',') { 1123 | this.readChar(); 1124 | this.scanNonSignficantData(); 1125 | return true; 1126 | } 1127 | this.scanFor(validStop); 1128 | this.index--; 1129 | return false; 1130 | } 1131 | 1132 | getRawObject = function() { 1133 | switch (this.readChar()) { 1134 | 1135 | case '<': 1136 | this.scanFor("<"); 1137 | let embedded = this.getObject(); 1138 | this.scanFor(">>"); 1139 | return CBOR.Bytes(embedded.encode()); 1140 | 1141 | case '[': 1142 | let array = CBOR.Array(); 1143 | this.scanNonSignficantData(); 1144 | while (this.readChar() != ']') { 1145 | this.index--; 1146 | do { 1147 | array.add(this.getObject()); 1148 | } while (this.continueList(']')); 1149 | } 1150 | return array; 1151 | 1152 | case '{': 1153 | let map = CBOR.Map(); 1154 | this.scanNonSignficantData(); 1155 | while (this.readChar() != '}') { 1156 | this.index--; 1157 | do { 1158 | let key = this.getObject(); 1159 | this.scanFor(":"); 1160 | map.set(key, this.getObject()); 1161 | } while (this.continueList('}')); 1162 | } 1163 | return map; 1164 | 1165 | case '\'': 1166 | return this.getString(true); 1167 | 1168 | case '"': 1169 | return this.getString(false); 1170 | 1171 | case 'h': 1172 | return this.getBytes(false); 1173 | 1174 | case 'b': 1175 | if (this.nextChar() == '3') { 1176 | this.scanFor("32'"); 1177 | this.reportError("b32 not implemented"); 1178 | } 1179 | this.scanFor("64"); 1180 | return this.getBytes(true); 1181 | 1182 | case 't': 1183 | this.scanFor("rue"); 1184 | return CBOR.Bool(true); 1185 | 1186 | case 'f': 1187 | this.scanFor("alse"); 1188 | return CBOR.Bool(false); 1189 | 1190 | case 'n': 1191 | this.scanFor("ull"); 1192 | return CBOR.Null(); 1193 | 1194 | case '-': 1195 | if (this.readChar() == 'I') { 1196 | this.scanFor("nfinity"); 1197 | return CBOR.Float(Number.NEGATIVE_INFINITY); 1198 | } 1199 | return this.getNumberOrTag(true); 1200 | 1201 | case '0': 1202 | case '1': 1203 | case '2': 1204 | case '3': 1205 | case '4': 1206 | case '5': 1207 | case '6': 1208 | case '7': 1209 | case '8': 1210 | case '9': 1211 | return this.getNumberOrTag(false); 1212 | 1213 | case 'N': 1214 | this.scanFor("aN"); 1215 | return CBOR.Float(Number.NaN); 1216 | 1217 | case 'I': 1218 | this.scanFor("nfinity"); 1219 | return CBOR.Float(Number.POSITIVE_INFINITY); 1220 | 1221 | default: 1222 | this.index--; 1223 | this.reportError("Unexpected character: " + this.toChar(this.readChar())); 1224 | } 1225 | } 1226 | 1227 | getNumberOrTag = function(negative) { 1228 | let token = ''; 1229 | this.index--; 1230 | let prefix = null; 1231 | if (this.readChar() == '0') { 1232 | switch (this.nextChar()) { 1233 | case 'b': 1234 | case 'o': 1235 | case 'x': 1236 | prefix = '0' + this.readChar(); 1237 | break; 1238 | } 1239 | } 1240 | if (prefix == null) { 1241 | this.index--; 1242 | } 1243 | let floatingPoint = false; 1244 | while (true) { 1245 | token += this.readChar(); 1246 | switch (this.nextChar()) { 1247 | case '\u0000': 1248 | case ' ': 1249 | case '\n': 1250 | case '\r': 1251 | case '\t': 1252 | case ',': 1253 | case ':': 1254 | case '>': 1255 | case ']': 1256 | case '}': 1257 | case '/': 1258 | case '#': 1259 | case '(': 1260 | case ')': 1261 | break; 1262 | 1263 | case '.': 1264 | floatingPoint = true; 1265 | continue; 1266 | 1267 | default: 1268 | continue; 1269 | } 1270 | break; 1271 | } 1272 | try { 1273 | if (floatingPoint) { 1274 | this.testForNonDecimal(prefix); 1275 | let value = Number(token); 1276 | // Implicit overflow is not permitted 1277 | if (!Number.isFinite(value)) { 1278 | throw RangeError("Floating point value out of range"); 1279 | } 1280 | return CBOR.Float(negative ? -value : value); 1281 | } 1282 | if (this.nextChar() == '(') { 1283 | // Do not accept '-', 0xhhh, or leading zeros 1284 | this.testForNonDecimal(prefix); 1285 | if (negative || (token.length() > 1 && token.charAt(0) == '0')) { 1286 | this.reportError("Tag syntax error"); 1287 | } 1288 | this.readChar(); 1289 | let tagNumber = BigInt(token); 1290 | let taggedbject = this.getObject(); 1291 | if (tagNumber == CBOR.Tag.RESERVED_TAG_COTX) { 1292 | if (!taggedbject instanceof CBOR.Array || taggedbject.size() != 2 || 1293 | !taggedbject.get(0) instanceof CBOR.String) { 1294 | this.reportError("Special tag " + CBOR.Tag.RESERVED_TAG_COTX + " syntax error"); 1295 | } 1296 | } 1297 | let cborTag = CBOR.Tag(tagNumber, taggedObject); 1298 | this.scanFor(")"); 1299 | return cborTag; 1300 | } 1301 | let bigInt = BigInt((prefix == null ? '' : prefix) + token); 1302 | // Clone: slight quirk to get the proper CBOR integer type 1303 | return CBOR.BigInt(negative ? -bigInt : bigInt).clone(); 1304 | } catch (error) { 1305 | this.reportError(error.toString()); 1306 | } 1307 | } 1308 | 1309 | testForNonDecimal = function(nonDecimal) { 1310 | if (nonDecimal) { 1311 | this.reportError("Hexadecimal not permitted here"); 1312 | } 1313 | } 1314 | 1315 | nextChar = function() { 1316 | if (this.index == this.cborText.length) return String.fromCharCode(0); 1317 | let c = this.readChar(); 1318 | this.index--; 1319 | return c; 1320 | } 1321 | 1322 | toChar = function(c) { 1323 | let charCode = c.charCodeAt(0); 1324 | return charCode < 0x20 ? "\\u00" + CBOR.#twoHex(charCode) : "'" + c + "'"; 1325 | } 1326 | 1327 | scanFor = function(expected) { 1328 | [...expected].forEach(c => { 1329 | let actual = this.readChar(); 1330 | if (c != actual) { 1331 | this.reportError("Expected: '" + c + "' actual: " + this.toChar(actual)); 1332 | } 1333 | }); 1334 | } 1335 | 1336 | getString = function(byteString) { 1337 | let s = ''; 1338 | while (true) { 1339 | let c; 1340 | switch (c = this.readChar()) { 1341 | // Multiline extension 1342 | case '\n': 1343 | case '\r': 1344 | case '\t': 1345 | break; 1346 | 1347 | case '\\': 1348 | switch (c = this.readChar()) { 1349 | case '\n': 1350 | continue; 1351 | 1352 | case '\'': 1353 | case '"': 1354 | case '\\': 1355 | break; 1356 | 1357 | case 'b': 1358 | c = '\b'; 1359 | break; 1360 | 1361 | case 'f': 1362 | c = '\f'; 1363 | break; 1364 | 1365 | case 'n': 1366 | c = '\n'; 1367 | break; 1368 | 1369 | case 'r': 1370 | c = '\r'; 1371 | break; 1372 | 1373 | case 't': 1374 | c = '\t'; 1375 | break; 1376 | 1377 | case 'u': 1378 | let u16 = 0; 1379 | for (let i = 0; i < 4; i++) { 1380 | u16 += (u16 << 4) + CBOR.#decodeOneHex(this.readChar().charCodeAt(0)); 1381 | } 1382 | c = String.fromCharCode(u16); 1383 | break; 1384 | 1385 | default: 1386 | this.reportError("Invalid escape character " + this.toChar(c)); 1387 | } 1388 | break; 1389 | 1390 | case '"': 1391 | if (!byteString) { 1392 | return CBOR.String(s); 1393 | } 1394 | break; 1395 | 1396 | case '\'': 1397 | if (byteString) { 1398 | return CBOR.Bytes(UTF8.encode(s)); 1399 | } 1400 | break; 1401 | 1402 | default: 1403 | if (c.charCodeAt(0) < 0x20) { 1404 | this.reportError("Unexpected control character: " + this.toChar(c)); 1405 | } 1406 | } 1407 | s += c; 1408 | } 1409 | } 1410 | 1411 | getBytes = function(b64) { 1412 | let token = ''; 1413 | this.scanFor("'"); 1414 | while(true) { 1415 | let c; 1416 | switch (c = this.readChar()) { 1417 | case '\'': 1418 | break; 1419 | 1420 | case ' ': 1421 | case '\r': 1422 | case '\n': 1423 | case '\t': 1424 | continue; 1425 | 1426 | default: 1427 | token += c; 1428 | continue; 1429 | } 1430 | break; 1431 | } 1432 | return CBOR.Bytes(b64 ? CBOR.fromBase64Url(token) : CBOR.fromHex(token)); 1433 | } 1434 | 1435 | hexCharToChar = function(c) { 1436 | 1437 | if (c >= '0' && c <= '9') { 1438 | return (char) (c - '0'); 1439 | } 1440 | if (c >= 'a' && c <= 'f') { 1441 | return (char) (c - 'a' + 10); 1442 | } 1443 | if (c >= 'A' && c <= 'F') { 1444 | return (char) (c - 'A' + 10); 1445 | } 1446 | reportError(String.format("Bad hex character: %s", toChar(c))); 1447 | return 0; // For the compiler... 1448 | } 1449 | 1450 | readChar = function() { 1451 | if (this.index >= this.cborText.length) { 1452 | this.reportError("Unexpected EOF"); 1453 | } 1454 | return this.cborText[this.index++]; 1455 | } 1456 | 1457 | scanNonSignficantData = function() { 1458 | while (this.index < this.cborText.length) { 1459 | switch (this.nextChar()) { 1460 | case ' ': 1461 | case '\n': 1462 | case '\r': 1463 | case '\t': 1464 | this.readChar(); 1465 | continue; 1466 | 1467 | case '/': 1468 | this.readChar(); 1469 | if (this.nextChar() != '/') { 1470 | while (this.readChar() != '/') { 1471 | } 1472 | continue; 1473 | } 1474 | // Yes, '//' is currently considered as equivalent to '#' 1475 | case '#': 1476 | this.readChar(); 1477 | while (this.index < this.cborText.length && this.readChar() != '\n') { 1478 | } 1479 | continue; 1480 | 1481 | default: 1482 | return; 1483 | } 1484 | } 1485 | } 1486 | } 1487 | 1488 | /////////////////////////////// 1489 | // CBOR.diagnosticNotation() // 1490 | /////////////////////////////// 1491 | 1492 | static diagnosticNotation = function(cborText, optionalSequenceFlag) { 1493 | if (optionalSequenceFlag) { 1494 | return new CBOR.#DiagnosticNotation(cborText, true).readSequenceToEOF(); 1495 | } else { 1496 | return new CBOR.#DiagnosticNotation(cborText, false).readToEOF(); 1497 | } 1498 | } 1499 | 1500 | //================================// 1501 | // Internal Support Methods // 1502 | //================================// 1503 | 1504 | static #encodeTagAndN = function(majorType, n) { 1505 | let modifier = n; 1506 | let length = 0; 1507 | if (n > 23) { 1508 | modifier = 24; 1509 | length = 1; 1510 | let nextRange = 0x100; 1511 | while (length < 8 && n >= nextRange) { 1512 | modifier++; 1513 | length <<= 1; 1514 | // The last multiplication will not be an integer but "length < 8" handles this. 1515 | nextRange *= nextRange; 1516 | } 1517 | } 1518 | let encoded = new Uint8Array(length + 1); 1519 | encoded[0] = majorType | modifier; 1520 | while (length > 0) { 1521 | encoded[length--] = n; 1522 | n /= 256; 1523 | } 1524 | return encoded; 1525 | } 1526 | 1527 | static #bytesCheck = function(byteArray) { 1528 | if (byteArray instanceof Uint8Array) { 1529 | return byteArray; 1530 | } 1531 | throw TypeError("Argument is not an 'Uint8Array'"); 1532 | } 1533 | 1534 | static #typeCheck = function(object, type) { 1535 | if (typeof object != type) { 1536 | throw TypeError("Argument is not a '" + type + "'"); 1537 | } 1538 | return object; 1539 | } 1540 | 1541 | static #intCheck = function(value) { 1542 | CBOR.#typeCheck(value, 'number'); 1543 | if (!Number.isSafeInteger(value)) { 1544 | if (Number.isInteger(value)) { 1545 | throw RangeError("Argument is outside of Number.MAX_SAFE_INTEGER"); 1546 | } else { 1547 | throw TypeError("Argument is not an integer"); 1548 | } 1549 | } 1550 | return value; 1551 | } 1552 | 1553 | static #finishBigIntAndTag = function(tag, value) { 1554 | // Convert BigInt to Uint8Array (but with a twist). 1555 | let array = []; 1556 | do { 1557 | array.push(Number(value & 255n)); 1558 | value >>= 8n; 1559 | } while (value); 1560 | let length = array.length; 1561 | // Prepare for "integer" encoding (1, 2, 4, 8). Only 3, 5, 6, and 7 need an action. 1562 | while (length < 8 && length > 2 && length != 4) { 1563 | array.push(0); 1564 | length++; 1565 | } 1566 | let byteArray = new Uint8Array(array.reverse()); 1567 | // Does this number qualify as a "big integer"? 1568 | if (length <= 8) { 1569 | // Apparently not, encode it as "integer". 1570 | if (length == 1 && byteArray[0] <= 23) { 1571 | return new Uint8Array([tag | byteArray[0]]); 1572 | } 1573 | let modifier = 24; 1574 | while (length >>= 1) { 1575 | modifier++; 1576 | } 1577 | return CBOR.addArrays(new Uint8Array([tag | modifier]), byteArray); 1578 | } 1579 | // True "BigInt". 1580 | return CBOR.addArrays(new Uint8Array([tag == CBOR.#MT_NEGATIVE ? 1581 | CBOR.#MT_BIG_NEGATIVE : CBOR.#MT_BIG_UNSIGNED]), 1582 | CBOR.Bytes(byteArray).encode()); 1583 | } 1584 | 1585 | static #Printer = class { 1586 | indentationLevel = 0; 1587 | 1588 | beginMap = function() { 1589 | this.indentationLevel++; 1590 | return '{'; 1591 | } 1592 | 1593 | newlineAndIndent = function() { 1594 | let buffer = '\n'; 1595 | for (let i = 0; i < this.indentationLevel; i++) { 1596 | buffer += ' '; 1597 | } 1598 | return buffer; 1599 | } 1600 | 1601 | endMap = function(notEmpty) { 1602 | this.indentationLevel--; 1603 | return notEmpty ? this.newlineAndIndent() + '}' : '}'; 1604 | } 1605 | } 1606 | 1607 | static #int16ToByteArray = function(int16) { 1608 | return new Uint8Array([int16 / 256, int16 % 256]); 1609 | } 1610 | 1611 | static #f64ToByteArray = function(value) { 1612 | const buffer = new ArrayBuffer(8); 1613 | new DataView(buffer).setFloat64(0, value, false); 1614 | return [].slice.call(new Uint8Array(buffer)) 1615 | } 1616 | 1617 | static #oneHex = function(digit) { 1618 | return String.fromCharCode(digit < 10 ? (0x30 + digit) : (0x57 + digit)); 1619 | } 1620 | 1621 | static #twoHex = function(byte) { 1622 | return CBOR.#oneHex(byte / 16) + CBOR.#oneHex(byte % 16); 1623 | } 1624 | 1625 | static #cborArguentCheck = function(object) { 1626 | if (object instanceof CBOR.#CBORObject) { 1627 | return object; 1628 | } 1629 | throw TypeError(object ? 1630 | "Argument is not a CBOR object: " + object.constructor.name : "'null'"); 1631 | } 1632 | 1633 | static #decodeOneHex(charCode) { 1634 | if (charCode >= 0x30 && charCode <= 0x39) return charCode - 0x30; 1635 | if (charCode >= 0x61 && charCode <= 0x66) return charCode - 0x57; 1636 | if (charCode >= 0x41 && charCode <= 0x46) return charCode - 0x37; 1637 | throw SyntaxError("Bad hex character: " + String.fromCharCode(charCode)); 1638 | } 1639 | 1640 | //================================// 1641 | // Public Support Methods // 1642 | //================================// 1643 | 1644 | static addArrays = function(a, b) { 1645 | let result = new Uint8Array(a.length + b.length); 1646 | let q = 0; 1647 | while (q < a.length) { 1648 | result[q] = a[q++]; 1649 | } 1650 | for (let i = 0; i < b.length; i++) { 1651 | result[q + i] = b[i]; 1652 | } 1653 | return result; 1654 | } 1655 | 1656 | static array 1657 | 1658 | static compareArrays = function(a, b) { 1659 | let minIndex = Math.min(a.length, b.length); 1660 | for (let i = 0; i < minIndex; i++) { 1661 | let diff = a[i] - b[i]; 1662 | if (diff != 0) { 1663 | return diff; 1664 | } 1665 | } 1666 | return a.length - b.length; 1667 | } 1668 | 1669 | static toHex = function (byteArray) { 1670 | let result = ''; 1671 | for (let i = 0; i < byteArray.length; i++) { 1672 | result += CBOR.#twoHex(byteArray[i]); 1673 | } 1674 | return result; 1675 | } 1676 | 1677 | static fromHex = function (hexString) { 1678 | let length = hexString.length; 1679 | if (length & 1) { 1680 | throw SyntaxError("Uneven number of characters in hex string"); 1681 | } 1682 | let result = new Uint8Array(length >> 1); 1683 | for (let q = 0; q < length; q += 2) { 1684 | result[q >> 1] = (CBOR.#decodeOneHex(hexString.charCodeAt(q)) << 4) + 1685 | CBOR.#decodeOneHex(hexString.charCodeAt(q + 1)); 1686 | } 1687 | return result; 1688 | } 1689 | 1690 | static toBase64Url = function(byteArray) { 1691 | return btoa(String.fromCharCode.apply(null, new Uint8Array(byteArray))) 1692 | .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 1693 | } 1694 | 1695 | static fromBase64Url = function(base64) { 1696 | if (!base64.includes('=')) { 1697 | base64 = base64 + '===='.substring(0, (4 - (base64.length % 4)) % 4); 1698 | } 1699 | return Uint8Array.from(atob(base64.replace(/-/g, '+').replace(/_/g, '/')), 1700 | c => c.charCodeAt(0)); 1701 | } 1702 | 1703 | } 1704 | 1705 | module.exports = CBOR; 1706 | --------------------------------------------------------------------------------