├── .gitignore ├── types ├── example.d.ts ├── lib │ ├── diagnostic_test.d.ts │ ├── bin.d.ts │ ├── bin.d.ts.map │ ├── diagnostic_test.d.ts.map │ ├── is.d.ts │ ├── is.d.ts.map │ ├── json │ │ ├── json.d.ts.map │ │ ├── encode.d.ts.map │ │ ├── json.d.ts │ │ ├── encode.d.ts │ │ ├── decode.d.ts.map │ │ └── decode.d.ts │ ├── diagnostic.d.ts.map │ ├── common.d.ts.map │ ├── length.d.ts.map │ ├── jump.d.ts.map │ ├── common.d.ts │ ├── bl.d.ts.map │ ├── diagnostic.d.ts │ ├── token.d.ts.map │ ├── decode.d.ts.map │ ├── jump.d.ts │ ├── 3string.d.ts.map │ ├── 1negint.d.ts.map │ ├── 6tag.d.ts.map │ ├── byte-utils.d.ts.map │ ├── 7float.d.ts.map │ ├── 5map.d.ts.map │ ├── 4array.d.ts.map │ ├── 2bytes.d.ts.map │ ├── bl.d.ts │ ├── encode.d.ts.map │ ├── 0uint.d.ts.map │ ├── length.d.ts │ ├── decode.d.ts │ ├── token.d.ts │ ├── 3string.d.ts │ ├── byte-utils.d.ts │ ├── 1negint.d.ts │ ├── encode.d.ts │ ├── 6tag.d.ts │ ├── 7float.d.ts │ ├── 5map.d.ts │ ├── 4array.d.ts │ ├── 2bytes.d.ts │ └── 0uint.d.ts ├── example.d.ts.map ├── taglib.d.ts.map ├── cborg.d.ts.map ├── taglib.d.ts ├── cborg.d.ts ├── interface.d.ts └── interface.d.ts.map ├── bench ├── package.json ├── bench.js └── json.js ├── lib ├── json │ ├── json.js │ └── encode.js ├── common.js ├── token.js ├── 6tag.js ├── length.js ├── is.js ├── 3string.js ├── 5map.js ├── 4array.js ├── 1negint.js ├── diagnostic_test.js ├── 2bytes.js ├── bl.js ├── diagnostic.js ├── bin.js ├── decode.js ├── 0uint.js ├── jump.js └── 7float.js ├── test ├── noop-bin-test.js ├── common.js ├── test-bl.js ├── test-fuzz.js ├── test-encode-rfc8949.js ├── test-length.js ├── test-decode-errors.js ├── test-6tag.js ├── test-cbor-vectors.js ├── test-partial.js ├── test-1negint.js ├── test-4array.js ├── test-0uint.js ├── test-3string.js └── test-7float.js ├── example.js ├── example-json.js ├── .github ├── dependabot.yml └── workflows │ └── test-and-release.yml ├── LICENSE ├── cborg.js ├── tsconfig.json ├── interface.ts ├── taglib.js ├── package.json └── example-bytestrings.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | coverage/ 4 | c/ 5 | dist/ 6 | -------------------------------------------------------------------------------- /types/example.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=example.d.ts.map -------------------------------------------------------------------------------- /types/lib/diagnostic_test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=diagnostic_test.d.ts.map -------------------------------------------------------------------------------- /types/example.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../example.js"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /types/lib/bin.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | declare const _default: true; 3 | export default _default; 4 | //# sourceMappingURL=bin.d.ts.map -------------------------------------------------------------------------------- /types/lib/bin.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../lib/bin.js"],"names":[],"mappings":";wBA4Le,IAAI"} -------------------------------------------------------------------------------- /types/lib/diagnostic_test.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"diagnostic_test.d.ts","sourceRoot":"","sources":["../../lib/diagnostic_test.js"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /types/lib/is.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {any} value 3 | * @returns {string} 4 | */ 5 | export function is(value: any): string; 6 | //# sourceMappingURL=is.d.ts.map -------------------------------------------------------------------------------- /types/lib/is.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"is.d.ts","sourceRoot":"","sources":["../../lib/is.js"],"names":[],"mappings":"AAiDA;;;GAGG;AACH,0BAHW,GAAG,GACD,MAAM,CAiClB"} -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cborg-bench", 3 | "type": "module", 4 | "dependencies": { 5 | "borc": "^2.1.2", 6 | "ipld-garbage": "^3.0.3" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/json/json.js: -------------------------------------------------------------------------------- 1 | import { encode } from './encode.js' 2 | import { decode, decodeFirst, Tokenizer } from './decode.js' 3 | 4 | export { encode, decode, decodeFirst, Tokenizer } 5 | -------------------------------------------------------------------------------- /types/lib/json/json.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../lib/json/json.js"],"names":[],"mappings":"uBAAuB,aAAa;uBACW,aAAa;4BAAb,aAAa;0BAAb,aAAa"} -------------------------------------------------------------------------------- /test/noop-bin-test.js: -------------------------------------------------------------------------------- 1 | // file included so ipjs will compile ../lib/bin.js 2 | // this test file is not intended to be run or loaded 3 | import bin from '../lib/bin.js' // eslint-disable-line 4 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | import { encode, decode } from 'cborg' 2 | 3 | const decoded = decode(Buffer.from('a16474686973a26269736543424f522163796179f5', 'hex')) 4 | console.log('decoded:', decoded) 5 | console.log('encoded:', encode(decoded)) 6 | -------------------------------------------------------------------------------- /types/lib/diagnostic.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"diagnostic.d.ts","sourceRoot":"","sources":["../../lib/diagnostic.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wCAHW,UAAU,UACV,MAAM,oCA4HhB;AAED;;;;GAIG;AACH,gCAHW,MAAM,GACJ,UAAU,CAatB"} -------------------------------------------------------------------------------- /types/lib/common.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../lib/common.js"],"names":[],"mappings":"AAAA,8BAAwB,oBAAoB,CAAA;AAC5C,8BAAwB,oBAAoB,CAAA;AAE5C,yCAA+B;AAO/B;;;;GAIG;AACH,uCAJW,UAAU,OACV,MAAM,QACN,MAAM,QAMhB"} -------------------------------------------------------------------------------- /types/lib/json/encode.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../../lib/json/encode.js"],"names":[],"mappings":"4BAMa,OAAO,iBAAiB,EAAE,aAAa;oBACvC,OAAO,UAAU,EAAE,KAAK;iBACxB,OAAO,OAAO,EAAE,EAAE;AAoS/B;;;;GAIG;AACH,6BAJW,GAAG,YACH,aAAa,GACX,UAAU,CAMtB"} -------------------------------------------------------------------------------- /types/taglib.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"taglib.d.ts","sourceRoot":"","sources":["../taglib.js"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,qCAHW,UAAU,GACR,MAAM,CASlB;AAmBD;;;GAGG;AACH,mCAHW,MAAM,GACJ,KAAK,EAAE,GAAC,IAAI,CAUxB;AAED;;;;GAIG;AACH,wCAHW,UAAU,GACR,MAAM,CAIlB;sBAxE2B,YAAY"} -------------------------------------------------------------------------------- /types/lib/json/json.d.ts: -------------------------------------------------------------------------------- 1 | import { encode } from './encode.js'; 2 | import { decode } from './decode.js'; 3 | import { decodeFirst } from './decode.js'; 4 | import { Tokenizer } from './decode.js'; 5 | export { encode, decode, decodeFirst, Tokenizer }; 6 | //# sourceMappingURL=json.d.ts.map -------------------------------------------------------------------------------- /types/cborg.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"cborg.d.ts","sourceRoot":"","sources":["../cborg.js"],"names":[],"mappings":";;;yBAMa,OAAO,aAAa,EAAE,UAAU;;;;0BAEhC,OAAO,aAAa,EAAE,mBAAmB;;;;4BACzC,OAAO,aAAa,EAAE,aAAa;;;;4BACnC,OAAO,aAAa,EAAE,aAAa;uBATe,iBAAiB;4BAAjB,iBAAiB;0BAAjB,iBAAiB;+BAAjB,iBAAiB;uBADzD,iBAAiB;sBAEZ,gBAAgB;qBAAhB,gBAAgB"} -------------------------------------------------------------------------------- /example-json.js: -------------------------------------------------------------------------------- 1 | import { encode, decode } from 'cborg/json' 2 | 3 | const decoded = decode(Buffer.from('7b2274686973223a7b226973223a224a534f4e21222c22796179223a747275657d7d', 'hex')) 4 | console.log('decoded:', decoded) 5 | console.log('encoded:', encode(decoded)) 6 | console.log('encoded (string):', Buffer.from(encode(decoded)).toString()) 7 | -------------------------------------------------------------------------------- /types/lib/length.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"length.d.ts","sourceRoot":"","sources":["../../lib/length.js"],"names":[],"mappings":"AAiBA;;;;;;;;;;GAUG;AACH,oCAJW,GAAG,YACH,aAAa,GACX,MAAM,CAOlB;AAED;;;;;;;;;GASG;AACH,uCAJW,mBAAmB,aACnB,gBAAgB,EAAE,YAClB,aAAa,UAiBvB;4BAxDY,OAAO,cAAc,EAAE,aAAa;+BACpC,OAAO,cAAc,EAAE,gBAAgB;kCACvC,OAAO,cAAc,EAAE,mBAAmB"} -------------------------------------------------------------------------------- /types/lib/jump.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"jump.d.ts","sourceRoot":"","sources":["../../lib/jump.js"],"names":[],"mappings":"AAkKA;;;GAGG;AACH,wCAHW,KAAK,GACH,UAAU,GAAC,SAAS,CA4ChC;AA/KD,6FAA6F;AAC7F,mBADW,CAAC,CAAC,IAAI,EAAC,UAAU,EAAE,GAAG,EAAC,MAAM,EAAE,KAAK,EAAC,MAAM,EAAE,OAAO,CAAC,EAAC,aAAa,KAAK,GAAG,CAAC,EAAE,CACnE;AAuGtB,sBAAsB;AACtB,oBADW,KAAK,EAAE,CACK;4BA7HV,OAAO,cAAc,EAAE,aAAa;sBAbrB,YAAY"} -------------------------------------------------------------------------------- /types/lib/common.d.ts: -------------------------------------------------------------------------------- 1 | export const decodeErrPrefix: "CBOR decode error:"; 2 | export const encodeErrPrefix: "CBOR encode error:"; 3 | export const uintMinorPrefixBytes: any[]; 4 | /** 5 | * @param {Uint8Array} data 6 | * @param {number} pos 7 | * @param {number} need 8 | */ 9 | export function assertEnoughData(data: Uint8Array, pos: number, need: number): void; 10 | //# sourceMappingURL=common.d.ts.map -------------------------------------------------------------------------------- /types/lib/json/encode.d.ts: -------------------------------------------------------------------------------- 1 | export type EncodeOptions = import("../../interface").EncodeOptions; 2 | export type Token = import("../token").Token; 3 | export type Bl = import("../bl").Bl; 4 | /** 5 | * @param {any} data 6 | * @param {EncodeOptions} [options] 7 | * @returns {Uint8Array} 8 | */ 9 | export function encode(data: any, options?: EncodeOptions): Uint8Array; 10 | //# sourceMappingURL=encode.d.ts.map -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | commit-message: 8 | prefix: 'chore' 9 | include: 'scope' 10 | - package-ecosystem: 'npm' 11 | directory: '/' 12 | schedule: 13 | interval: 'daily' 14 | commit-message: 15 | prefix: 'chore' 16 | include: 'scope' 17 | -------------------------------------------------------------------------------- /types/lib/bl.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bl.d.ts","sourceRoot":"","sources":["../../lib/bl.js"],"names":[],"mappings":"AA0BA;IACE;;OAEG;IACH,wBAFW,MAAM,EAahB;IAVC,kBAA0B;IAC1B,qBAAqB;IACrB,QADW,MAAM,CACF;IACf,qBAAqB;IACrB,WADW,MAAM,CACE;IACnB,sCAAsC;IACtC,QADW,CAAC,UAAU,GAAC,MAAM,EAAE,CAAC,EAAE,CAClB;IAEhB,uCAAuC;IACvC,iBADW,UAAU,GAAC,MAAM,EAAE,GAAC,IAAI,CACR;IAG7B,cAUC;IAED;;OAEG;IACH,YAFW,UAAU,GAAC,MAAM,EAAE,QAsC7B;IAED;;;OAGG;IACH,gBAHW,OAAO,GACL,UAAU,CAwBtB;CACF"} -------------------------------------------------------------------------------- /types/lib/diagnostic.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} inp 3 | * @param {number} [width] 4 | */ 5 | export function tokensToDiagnostic(inp: Uint8Array, width?: number): Generator; 6 | /** 7 | * Convert an input string formatted as CBOR diagnostic output into binary CBOR form. 8 | * @param {string} input 9 | * @returns {Uint8Array} 10 | */ 11 | export function fromDiag(input: string): Uint8Array; 12 | //# sourceMappingURL=diagnostic.d.ts.map -------------------------------------------------------------------------------- /types/lib/token.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../lib/token.js"],"names":[],"mappings":"AAAA;IACE;;;;OAIG;IACH,mBAJW,MAAM,QACN,MAAM,YACN,OAAO,EAOjB;IAJC,cAAkB;IAClB,qBAA8B;IAC9B,aAAgB;IAChB,kBAAwB;IAI1B,mBAEC;IAED;;;OAGG;IACH,aAHW,IAAI,GACF,MAAM,CAKlB;CACF;;;;;;;;;;;;;;;;;;;;AAkBD;IACE;;;;OAIG;IACH,kBAJW,IAAI,UACJ,GAAG,kBACH,MAAM,EAUhB;IAPC,WAAgB;IAChB,WAAkB;IAClB,kCAAkC;IAClC,mCAAmC;IACnC,cADW,UAAU,GAAC,SAAS,CACF;IAC7B,mCAAmC;IACnC,WADW,UAAU,GAAC,SAAS,CACL;IAI5B,mBAEC;CACF"} -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from '../lib/token.js' 2 | 3 | export function dateDecoder (obj) { 4 | if (typeof obj !== 'string') { 5 | throw new Error('expected string for tag 1') 6 | } 7 | return new Date(obj) 8 | } 9 | 10 | export function dateEncoder (obj) { 11 | if (!(obj instanceof Date)) { 12 | throw new Error('expected Date for "Date" encoder') 13 | } 14 | return [ 15 | new Token(Type.tag, 0), 16 | new Token(Type.string, obj.toISOString().replace(/\.000Z$/, 'Z')) 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /types/lib/decode.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../lib/decode.js"],"names":[],"mappings":"oBAKa,OAAO,YAAY,EAAE,KAAK;4BAC1B,OAAO,cAAc,EAAE,aAAa;8BACpC,OAAO,cAAc,EAAE,eAAe;AAUnD;;GAEG;AACH,kCAFgB,eAAe;IAG7B;;;OAGG;IACH,kBAHW,UAAU,YACV,aAAa,EAMvB;IAHC,aAAa;IACb,kCAAgB;IAChB,8CAAsB;IAGxB,cAEC;IAED,gBAEC;IAED,mCAgBC;CACF;AA6ED;;;;GAIG;AACH,0CAJW,eAAe,WACf,aAAa,GACX,GAAG,6BAAW,CAoC1B;AAuBD;;;;GAIG;AACH,6BAJW,UAAU,YACV,aAAa,GACX,GAAG,CAQf;AAhCD;;;;GAIG;AACH,kCAJW,UAAU,YACV,aAAa,GACX,CAAC,GAAG,EAAE,UAAU,CAAC,CAgB7B;AAtID,mCAAiC;AADjC,kCAA+B"} -------------------------------------------------------------------------------- /types/lib/jump.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Token} token 3 | * @returns {Uint8Array|undefined} 4 | */ 5 | export function quickEncodeToken(token: Token): Uint8Array | undefined; 6 | /** @type {((data:Uint8Array, pos:number, minor:number, options?:DecodeOptions) => any)[]} */ 7 | export const jump: ((data: Uint8Array, pos: number, minor: number, options?: DecodeOptions) => any)[]; 8 | /** @type {Token[]} */ 9 | export const quick: Token[]; 10 | export type DecodeOptions = import("../interface").DecodeOptions; 11 | import { Token } from './token.js'; 12 | //# sourceMappingURL=jump.d.ts.map -------------------------------------------------------------------------------- /types/taglib.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} bytes 3 | * @returns {bigint} 4 | */ 5 | export function bigIntDecoder(bytes: Uint8Array): bigint; 6 | /** 7 | * @param {bigint} obj 8 | * @returns {Token[]|null} 9 | */ 10 | export function bigIntEncoder(obj: bigint): Token[] | null; 11 | /** 12 | * TAG(3) Negative Bignums https://tools.ietf.org/html/rfc8949#section-3.4.3 13 | * @param {Uint8Array} bytes 14 | * @returns {bigint} 15 | */ 16 | export function bigNegIntDecoder(bytes: Uint8Array): bigint; 17 | import { Token } from './cborg.js'; 18 | //# sourceMappingURL=taglib.d.ts.map -------------------------------------------------------------------------------- /types/lib/3string.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"3string.d.ts","sourceRoot":"","sources":["../../lib/3string.js"],"names":[],"mappings":"AA6BA;;;;;;GAMG;AACH,0CANW,UAAU,OACV,MAAM,SACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAGD;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAQjB;AAED,8CAAuC;iBAlF1B,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBARrB,YAAY;4BAGZ,aAAa"} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Rod Vagg 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /types/lib/1negint.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"1negint.d.ts","sourceRoot":"","sources":["../../lib/1negint.js"],"names":[],"mappings":"AAMA;;;GAGG;AAEH;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAKD;;;;;;GAMG;AACH,qCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAcjB;AAED;;;GAGG;AACH,kCAHW,EAAE,SACF,KAAK,QAMf;;IAED;;;OAGG;IACH,4BAHW,KAAK,GACH,MAAM,CAoBlB;IAED;;;;OAIG;IACH,6BAJW,KAAK,QACL,KAAK,GACH,MAAM,CAKlB;;iBAvGY,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBANrB,YAAY"} -------------------------------------------------------------------------------- /types/lib/6tag.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"6tag.d.ts","sourceRoot":"","sources":["../../lib/6tag.js"],"names":[],"mappings":"AAGA;;;GAGG;AAEH;;;;;;GAMG;AACH,wCANW,UAAU,QACV,MAAM,SACN,MAAM,YACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,iCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;GAGG;AACH,+BAHW,EAAE,SACF,KAAK,QAIf;;;IAID;;;OAGG;IACH,4BAHW,KAAK,GACH,MAAM,CAIlB;;iBA3EY,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBALrB,YAAY"} -------------------------------------------------------------------------------- /types/lib/byte-utils.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"byte-utils.d.ts","sourceRoot":"","sources":["../../lib/byte-utils.js"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,2BAHW,UAAU,GAAC,MAAM,EAAE,GACjB,UAAU,CAQtB;AA8ND;;;;GAIG;AACH,4BAJW,UAAU,MACV,UAAU,GACR,MAAM,CAgBlB;AAyHD;;;GAGG;AACH,kDAHW,MAAM,EAAE,GACN,MAAM,CAkBlB;AA5ZD,gCAMkD;AA4B9C;;;;GAIG;AACH,gCAJW,UAAU,SACV,MAAM,OACN,MAAM,UAQhB;AAcL,mCAGe,MAAM,iDAYN,MAAM,6CAIhB;AAOE,+BAHI,MAAM,EAAE,GACN,UAAU,CAItB;AAIG;;;;GAIG;AACH,6BAJW,UAAU,SACV,MAAM,OACN,MAAM,2BAOhB;AAcD;;;;GAIG;AACH,+BAJW,UAAU,EAAE,UACZ,MAAM,GACJ,UAAU,CActB;AAwBD;;;GAGG;AACH,4BAHW,MAAM,GACJ,UAAU,CAMtB;AAaD;;;GAGG;AACH,yBAHW,UAAU,GACR,MAAM,CAQlB;AAiBH;;;GAGG;AACD,6BAHS,MAAM,GAAC,UAAU,GACf,UAAU,CAQpB"} -------------------------------------------------------------------------------- /types/lib/7float.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"7float.d.ts","sourceRoot":"","sources":["../../lib/7float.js"],"names":[],"mappings":"AAkBA;;;;;;GAMG;AACH,uCANW,UAAU,QACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CASjB;AAED;;;;;;GAMG;AACH,mCANW,UAAU,QACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAOjB;AAoBD;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;GAIG;AACH,iCAJW,EAAE,SACF,KAAK,WACL,aAAa,QAwCvB;;IAED;;;;OAIG;IACH,4BAJW,KAAK,WACL,aAAa,GACX,MAAM,CAsBlB;;;iBAjKY,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;4BACpC,OAAO,cAAc,EAAE,aAAa;sBAPrB,YAAY"} -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | const decodeErrPrefix = 'CBOR decode error:' 2 | const encodeErrPrefix = 'CBOR encode error:' 3 | 4 | const uintMinorPrefixBytes = [] 5 | uintMinorPrefixBytes[23] = 1 6 | uintMinorPrefixBytes[24] = 2 7 | uintMinorPrefixBytes[25] = 3 8 | uintMinorPrefixBytes[26] = 5 9 | uintMinorPrefixBytes[27] = 9 10 | 11 | /** 12 | * @param {Uint8Array} data 13 | * @param {number} pos 14 | * @param {number} need 15 | */ 16 | function assertEnoughData (data, pos, need) { 17 | if (data.length - pos < need) { 18 | throw new Error(`${decodeErrPrefix} not enough data for type`) 19 | } 20 | } 21 | 22 | export { 23 | decodeErrPrefix, 24 | encodeErrPrefix, 25 | uintMinorPrefixBytes, 26 | assertEnoughData 27 | } 28 | -------------------------------------------------------------------------------- /types/lib/5map.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"5map.d.ts","sourceRoot":"","sources":["../../lib/5map.js"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,uCANW,UAAU,OACV,MAAM,SACN,MAAM,YACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,iCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAGD;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAQjB;AAED;;;;;;GAMG;AACH,0CANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAOjB;AAED;;;GAGG;AACH,+BAHW,EAAE,SACF,KAAK,QAIf;;;IAMD;;;OAGG;IACH,4BAHW,KAAK,GACH,MAAM,CAIlB;;iBA3GY,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBANrB,YAAY"} -------------------------------------------------------------------------------- /types/lib/4array.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"4array.d.ts","sourceRoot":"","sources":["../../lib/4array.js"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,yCANW,UAAU,OACV,MAAM,SACN,MAAM,YACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,mCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAGD;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAQjB;AAED;;;;;;GAMG;AACH,4CANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAOjB;AAED;;;GAGG;AACH,iCAHW,EAAE,SACF,KAAK,QAIf;;;IAMD;;;OAGG;IACH,4BAHW,KAAK,GACH,MAAM,CAIlB;;iBA3GY,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBANrB,YAAY"} -------------------------------------------------------------------------------- /types/lib/2bytes.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"2bytes.d.ts","sourceRoot":"","sources":["../../lib/2bytes.js"],"names":[],"mappings":"AAuBA;;;;;;GAMG;AACH,yCANW,UAAU,OACV,MAAM,SACN,MAAM,YACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,mCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAGD;;;;;;GAMG;AACH,oCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAQjB;AAgBD;;;GAGG;AACH,iCAHW,EAAE,SACF,KAAK,QAMf;;IAED;;;OAGG;IACH,4BAHW,KAAK,GACH,MAAM,CAKlB;IAED;;;;OAIG;IACH,6BAJW,KAAK,QACL,KAAK,GACH,MAAM,CAIlB;;AAED;;;;GAIG;AACH,iCAJW,UAAU,MACV,UAAU,GACR,MAAM,CAIlB;iBA9HY,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBAPrB,YAAY"} -------------------------------------------------------------------------------- /types/lib/json/decode.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../../lib/json/decode.js"],"names":[],"mappings":"4BAMa,OAAO,iBAAiB,EAAE,aAAa;8BACvC,OAAO,iBAAiB,EAAE,eAAe;AAgbtD;;;;GAIG;AACH,6BAJW,UAAU,YACV,aAAa,GACX,GAAG,CAKf;AAED;;;;GAIG;AACH,kCAJW,UAAU,YACV,aAAa,GACX,CAAC,GAAG,EAAE,UAAU,CAAC,CAK7B;AApcD;;;GAGG;AAEH;;GAEG;AACH,kCAFgB,eAAe;IAG7B;;;OAGG;IACH,kBAHW,UAAU,YACV,aAAa,EASvB;IANC,aAAa;IACb,kCAAgB;IAChB,iDAAsB;IACtB,uBAAuB;IACvB,WADW,MAAM,EAAE,CACO;IAC1B,kBAAmB;IAGrB,cAEC;IAED;;OAEG;IACH,QAFa,OAAO,CAInB;IAED;;OAEG;IACH,MAFa,MAAM,CAIlB;IAED;;OAEG;IACH,eAFa,MAAM,CAIlB;IAED,uBAMC;IAED;;OAEG;IACH,YAFW,MAAM,EAAE,QAWlB;IAED,qBA+DC;IAED;;OAEG;IACH,eAFa,KAAK,CAkLjB;IAED;;OAEG;IACH,cAFa,KAAK,CAuCjB;IAED;;OAEG;IACH,QAFa,KAAK,CAyEjB;CACF;sBApb2B,aAAa"} -------------------------------------------------------------------------------- /types/lib/bl.d.ts: -------------------------------------------------------------------------------- 1 | export class Bl { 2 | /** 3 | * @param {number} [chunkSize] 4 | */ 5 | constructor(chunkSize?: number); 6 | chunkSize: number; 7 | /** @type {number} */ 8 | cursor: number; 9 | /** @type {number} */ 10 | maxCursor: number; 11 | /** @type {(Uint8Array|number[])[]} */ 12 | chunks: (Uint8Array | number[])[]; 13 | /** @type {Uint8Array|number[]|null} */ 14 | _initReuseChunk: Uint8Array | number[] | null; 15 | reset(): void; 16 | /** 17 | * @param {Uint8Array|number[]} bytes 18 | */ 19 | push(bytes: Uint8Array | number[]): void; 20 | /** 21 | * @param {boolean} [reset] 22 | * @returns {Uint8Array} 23 | */ 24 | toBytes(reset?: boolean): Uint8Array; 25 | } 26 | //# sourceMappingURL=bl.d.ts.map -------------------------------------------------------------------------------- /types/lib/encode.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../lib/encode.js"],"names":[],"mappings":"AAgCA,oCAAoC;AACpC,oCADc,gBAAgB,EAAE,CAY/B;4BA3BY,OAAO,cAAc,EAAE,aAAa;kCACpC,OAAO,cAAc,EAAE,mBAAmB;wBAC1C,OAAO,cAAc,EAAE,SAAS;gCAChC,OAAO,cAAc,EAAE,iBAAiB;+BACxC,OAAO,cAAc,EAAE,gBAAgB;kCACvC,OAAO,cAAc,EAAE,mBAAmB;AAgQvD;;;;;GAKG;AACH,oCALW,GAAG,YACH,aAAa,aACb,SAAS,GACP,mBAAmB,CAgB/B;AA2JD;;;;GAIG;AACH,6BAJW,GAAG,YACH,aAAa,GACX,UAAU,CAKtB;AAvCD;;;;;GAKG;AACH,mCALW,GAAG,YACH,gBAAgB,EAAE,WAClB,aAAa,GACX,UAAU,CAyBtB;AAjZD,8BAA8B;AAC9B,4BADiB,SAAS;IA0BxB;;;;OAIG;IACH,0BAJW,SAAS,GAAC,SAAS,OACnB,MAAM,GAAC,GAAG,EAAE,GACV,SAAS,CAOrB;IAlCD;;;OAGG;IACH,iBAHW,MAAM,GAAC,GAAG,EAAE,UACZ,SAAS,GAAC,SAAS,EAK7B;IAFC,oBAAc;IACd,qDAAoB;IAGtB;;;OAGG;IACH,cAHW,MAAM,GAAC,GAAG,EAAE,GACV,OAAO,CAWnB;CAaF"} -------------------------------------------------------------------------------- /cborg.js: -------------------------------------------------------------------------------- 1 | import { encode, rfc8949EncodeOptions } from './lib/encode.js' 2 | import { decode, decodeFirst, Tokeniser, tokensToObject } from './lib/decode.js' 3 | import { Token, Type } from './lib/token.js' 4 | 5 | /** 6 | * Export the types that were present in the original manual cborg.d.ts 7 | * @typedef {import('./interface').TagDecoder} TagDecoder 8 | * There was originally just `TypeEncoder` so don't break types by renaming or not exporting 9 | * @typedef {import('./interface').OptionalTypeEncoder} TypeEncoder 10 | * @typedef {import('./interface').DecodeOptions} DecodeOptions 11 | * @typedef {import('./interface').EncodeOptions} EncodeOptions 12 | */ 13 | 14 | export { 15 | decode, 16 | decodeFirst, 17 | Tokeniser as Tokenizer, 18 | tokensToObject, 19 | encode, 20 | rfc8949EncodeOptions, 21 | Token, 22 | Type 23 | } 24 | -------------------------------------------------------------------------------- /types/lib/0uint.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"0uint.d.ts","sourceRoot":"","sources":["../../lib/0uint.js"],"names":[],"mappings":"AAOA;;;GAGG;AAEH;;;;;GAKG;AACH,gCALW,UAAU,UACV,MAAM,WACN,aAAa,GACX,MAAM,CASlB;AAED;;;;;GAKG;AACH,iCALW,UAAU,UACV,MAAM,WACN,aAAa,GACX,MAAM,CASlB;AAED;;;;;GAKG;AACH,iCALW,UAAU,UACV,MAAM,WACN,aAAa,GACX,MAAM,CASlB;AAED;;;;;GAKG;AACH,iCALW,UAAU,UACV,MAAM,WACN,aAAa,GACX,MAAM,GAAC,MAAM,CAkBzB;AASD;;;;;;GAMG;AACH,kCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,mCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,mCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;;;;GAMG;AACH,mCANW,UAAU,OACV,MAAM,UACN,MAAM,WACN,aAAa,GACX,KAAK,CAIjB;AAED;;;GAGG;AACH,gCAHW,EAAE,SACF,KAAK,QAIf;;IAqDD;;;OAGG;IACH,4BAHW,KAAK,GACH,MAAM,CAIlB;IAsBD;;;;OAIG;IACH,6BAJW,KAAK,QACL,KAAK,GACH,MAAM,CAIlB;;AAtFD;;;;GAIG;AACH,qCAJW,EAAE,SACF,MAAM,QACN,MAAM,GAAC,MAAM,QA8CvB;;IAUD;;;OAGG;IACH,2BAHW,MAAM,GACJ,MAAM,CAgBlB;;AApND,iDAA0F;iBAG7E,OAAO,SAAS,EAAE,EAAE;4BACpB,OAAO,cAAc,EAAE,aAAa;sBAPrB,YAAY"} -------------------------------------------------------------------------------- /types/cborg.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * There was originally just `TypeEncoder` so don't break types by renaming or not exporting 3 | */ 4 | export type TagDecoder = import("./interface").TagDecoder; 5 | /** 6 | * Export the types that were present in the original manual cborg.d.ts 7 | */ 8 | export type TypeEncoder = import("./interface").OptionalTypeEncoder; 9 | /** 10 | * Export the types that were present in the original manual cborg.d.ts 11 | */ 12 | export type DecodeOptions = import("./interface").DecodeOptions; 13 | /** 14 | * Export the types that were present in the original manual cborg.d.ts 15 | */ 16 | export type EncodeOptions = import("./interface").EncodeOptions; 17 | import { decode } from './lib/decode.js'; 18 | import { decodeFirst } from './lib/decode.js'; 19 | import { Tokeniser } from './lib/decode.js'; 20 | import { tokensToObject } from './lib/decode.js'; 21 | import { encode } from './lib/encode.js'; 22 | import { Token } from './lib/token.js'; 23 | import { Type } from './lib/token.js'; 24 | export { decode, decodeFirst, Tokeniser as Tokenizer, tokensToObject, encode, Token, Type }; 25 | //# sourceMappingURL=cborg.d.ts.map -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "noImplicitReturns": false, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "strictFunctionTypes": false, 13 | "strictNullChecks": true, 14 | "strictPropertyInitialization": true, 15 | "strictBindCallApply": true, 16 | "strict": true, 17 | "alwaysStrict": true, 18 | "esModuleInterop": true, 19 | "target": "ES2018", 20 | "moduleResolution": "node", 21 | "declaration": true, 22 | "declarationMap": true, 23 | "outDir": "types", 24 | "skipLibCheck": true, 25 | "stripInternal": true, 26 | "resolveJsonModule": true, 27 | "baseUrl": ".", 28 | "emitDeclarationOnly": true, 29 | "paths": { 30 | "cborg": [ 31 | "cborg.js" 32 | ] 33 | } 34 | }, 35 | "include": [ 36 | "cborg.js", 37 | "example.js", 38 | "taglib.js", 39 | "lib/" 40 | ], 41 | "exclude": [ 42 | "node_modules" 43 | ], 44 | "compileOnSave": false 45 | } 46 | -------------------------------------------------------------------------------- /test/test-bl.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | import { Bl } from '../lib/bl.js' 5 | 6 | const { assert } = chai 7 | 8 | describe('Internal bytes list', () => { 9 | describe('push', () => { 10 | it('push bits', () => { 11 | const bl = new Bl(10) 12 | const expected = [] 13 | for (let i = 0; i < 25; i++) { 14 | bl.push([i + 1]) 15 | expected.push(i + 1) 16 | } 17 | assert.deepEqual([...bl.toBytes()], expected) 18 | }) 19 | 20 | for (let i = 4; i < 21; i++) { 21 | it(`push Bl(${i})`, () => { 22 | const bl = new Bl(i) 23 | const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 110, 120, 11, 12, 130, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 24 | for (let i = 0; i < 5; i++) { 25 | bl.push([i + 1]) 26 | } 27 | bl.push(Uint8Array.from([6, 7, 8, 9, 10])) 28 | bl.push([100]) 29 | bl.push(Uint8Array.from([110, 120])) 30 | bl.push(Uint8Array.from([11, 12])) 31 | bl.push([130]) 32 | bl.push(Uint8Array.from([13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23])) 33 | assert.deepEqual([...bl.toBytes()], expected) 34 | }) 35 | } 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /types/lib/length.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate the byte length of the given data when encoded as CBOR with the 3 | * options provided. 4 | * This calculation will be accurate if the same options are used as when 5 | * performing a normal encode. Some encode options can change the encoding 6 | * output length. 7 | * 8 | * @param {any} data 9 | * @param {EncodeOptions} [options] 10 | * @returns {number} 11 | */ 12 | export function encodedLength(data: any, options?: EncodeOptions): number; 13 | /** 14 | * Calculate the byte length of the data as represented by the given tokens when 15 | * encoded as CBOR with the options provided. 16 | * This function is for advanced users and would not normally be called 17 | * directly. See `encodedLength()` for appropriate use. 18 | * 19 | * @param {TokenOrNestedTokens} tokens 20 | * @param {TokenTypeEncoder[]} [encoders] 21 | * @param {EncodeOptions} [options] 22 | */ 23 | export function tokensToLength(tokens: TokenOrNestedTokens, encoders?: TokenTypeEncoder[], options?: EncodeOptions): number; 24 | export type EncodeOptions = import("../interface").EncodeOptions; 25 | export type TokenTypeEncoder = import("../interface").TokenTypeEncoder; 26 | export type TokenOrNestedTokens = import("../interface").TokenOrNestedTokens; 27 | //# sourceMappingURL=length.d.ts.map -------------------------------------------------------------------------------- /test/test-fuzz.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import { garbage } from 'ipld-garbage' 4 | import { decode, encode } from '../cborg.js' 5 | import * as chai from 'chai' 6 | 7 | const { assert } = chai 8 | 9 | describe('Fuzz round-trip', () => { 10 | it('random objects', function () { 11 | this.timeout(5000) 12 | for (let i = 0; i < 1000; i++) { 13 | const obj = garbage(300, { weights: { CID: 0 } }) 14 | const byts = encode(obj) 15 | const decoded = decode(byts) 16 | assert.deepEqual(decoded, obj) 17 | } 18 | }) 19 | 20 | it('circular references error', () => { 21 | let obj = {} 22 | obj.obj = obj 23 | assert.throws(() => encode(obj), /circular references/) 24 | 25 | obj = { blip: [1, 2, { blop: {} }] } 26 | obj.blip[2].blop.boop = obj 27 | assert.throws(() => encode(obj), /circular references/) 28 | 29 | obj = { blip: [1, 2, { blop: {} }] } 30 | obj.blip[2].blop.boop = obj.blip 31 | assert.throws(() => encode(obj), /circular references/) 32 | 33 | // not circular but a naive check might decide it is 34 | obj = { blip: {}, bloop: {} } 35 | obj.bloop = obj.blip 36 | assert.doesNotThrow(() => encode(obj)) 37 | 38 | const arr = [] 39 | arr[0] = arr 40 | assert.throws(() => encode(arr), /circular references/) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/test-encode-rfc8949.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | import { encode, rfc8949EncodeOptions } from '../cborg.js' 5 | import { toHex } from '../lib/byte-utils.js' 6 | 7 | const { assert } = chai 8 | 9 | describe('RFC8949 deterministic encoding', () => { 10 | it('should encode', () => { 11 | const value = new Map() 12 | // https://datatracker.ietf.org/doc/html/rfc8949#section-4.2.1 13 | value.set(false, false) 14 | // value.set([-1], [-1]) 15 | // value.set([100], [100]) 16 | value.set('aa', 'aa') 17 | value.set('z', 'z') 18 | value.set(-1, -1) 19 | value.set(10, 10) 20 | value.set(100, 100) 21 | 22 | const data = encode(value, rfc8949EncodeOptions) 23 | assert.deepEqual( 24 | toHex(data), 25 | 'a60a0a186418642020617a617a626161626161f4f4' 26 | ) 27 | // {10: 10, 100: 100, -1: -1, "z": "z", "aa": "aa", false: false} 28 | }) 29 | 30 | it('will throw on complex key types', () => { 31 | const value = new Map() 32 | // https://datatracker.ietf.org/doc/html/rfc8949#section-4.2.1 33 | value.set(false, false) 34 | value.set([-1], [-1]) 35 | value.set([100], [100]) 36 | value.set('aa', 'aa') 37 | value.set('z', 'z') 38 | value.set(-1, -1) 39 | value.set(10, 10) 40 | value.set(100, 100) 41 | 42 | assert.throws(() => { 43 | encode(value, rfc8949EncodeOptions) 44 | }) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /types/lib/decode.d.ts: -------------------------------------------------------------------------------- 1 | export type Token = import("./token.js").Token; 2 | export type DecodeOptions = import("../interface").DecodeOptions; 3 | export type DecodeTokenizer = import("../interface").DecodeTokenizer; 4 | /** 5 | * @implements {DecodeTokenizer} 6 | */ 7 | export class Tokeniser implements DecodeTokenizer { 8 | /** 9 | * @param {Uint8Array} data 10 | * @param {DecodeOptions} options 11 | */ 12 | constructor(data: Uint8Array, options?: DecodeOptions); 13 | _pos: number; 14 | data: Uint8Array; 15 | options: import("../interface").DecodeOptions; 16 | pos(): number; 17 | done(): boolean; 18 | next(): import("./token.js").Token; 19 | } 20 | /** 21 | * @param {DecodeTokenizer} tokeniser 22 | * @param {DecodeOptions} options 23 | * @returns {any|BREAK|DONE} 24 | */ 25 | export function tokensToObject(tokeniser: DecodeTokenizer, options: DecodeOptions): any | typeof BREAK | typeof DONE; 26 | /** 27 | * @param {Uint8Array} data 28 | * @param {DecodeOptions} [options] 29 | * @returns {any} 30 | */ 31 | export function decode(data: Uint8Array, options?: DecodeOptions): any; 32 | /** 33 | * @param {Uint8Array} data 34 | * @param {DecodeOptions} [options] 35 | * @returns {[any, Uint8Array]} 36 | */ 37 | export function decodeFirst(data: Uint8Array, options?: DecodeOptions): [any, Uint8Array]; 38 | declare const BREAK: unique symbol; 39 | declare const DONE: unique symbol; 40 | export {}; 41 | //# sourceMappingURL=decode.d.ts.map -------------------------------------------------------------------------------- /types/lib/token.d.ts: -------------------------------------------------------------------------------- 1 | export class Type { 2 | /** 3 | * @param {number} major 4 | * @param {string} name 5 | * @param {boolean} terminal 6 | */ 7 | constructor(major: number, name: string, terminal: boolean); 8 | major: number; 9 | majorEncoded: number; 10 | name: string; 11 | terminal: boolean; 12 | toString(): string; 13 | /** 14 | * @param {Type} typ 15 | * @returns {number} 16 | */ 17 | compare(typ: Type): number; 18 | } 19 | export namespace Type { 20 | export let uint: Type; 21 | export let negint: Type; 22 | export let bytes: Type; 23 | export let string: Type; 24 | export let array: Type; 25 | export let map: Type; 26 | export let tag: Type; 27 | export let float: Type; 28 | let _false: Type; 29 | export { _false as false }; 30 | let _true: Type; 31 | export { _true as true }; 32 | let _null: Type; 33 | export { _null as null }; 34 | export let undefined: Type; 35 | let _break: Type; 36 | export { _break as break }; 37 | } 38 | export class Token { 39 | /** 40 | * @param {Type} type 41 | * @param {any} [value] 42 | * @param {number} [encodedLength] 43 | */ 44 | constructor(type: Type, value?: any, encodedLength?: number); 45 | type: Type; 46 | value: any; 47 | encodedLength: number | undefined; 48 | /** @type {Uint8Array|undefined} */ 49 | encodedBytes: Uint8Array | undefined; 50 | /** @type {Uint8Array|undefined} */ 51 | byteValue: Uint8Array | undefined; 52 | toString(): string; 53 | } 54 | //# sourceMappingURL=token.d.ts.map -------------------------------------------------------------------------------- /.github/workflows/test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Test & Maybe Release 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | node: [lts/*, current] 9 | os: [macos-latest, ubuntu-latest, windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - name: Checkout Repository 13 | uses: actions/checkout@v6 14 | - name: Use Node.js ${{ matrix.node }} 15 | uses: actions/setup-node@v6.1.0 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - name: Install Dependencies 19 | run: | 20 | npm install --no-progress 21 | - name: Run tests 22 | run: | 23 | npm config set script-shell bash 24 | npm run test:ci 25 | - name: Typecheck 26 | uses: gozala/typescript-error-reporter-action@v1.0.9 27 | release: 28 | name: Release 29 | needs: test 30 | runs-on: ubuntu-latest 31 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v6 35 | with: 36 | fetch-depth: 0 37 | - name: Setup Node.js 38 | uses: actions/setup-node@v6.1.0 39 | with: 40 | node-version: lts/* 41 | - name: Install dependencies 42 | run: | 43 | npm install --no-progress --no-package-lock --no-save 44 | - name: Build 45 | run: | 46 | npm run build 47 | - name: Release 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 51 | run: npx semantic-release 52 | 53 | -------------------------------------------------------------------------------- /types/lib/3string.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} data 3 | * @param {number} pos 4 | * @param {number} minor 5 | * @param {DecodeOptions} options 6 | * @returns {Token} 7 | */ 8 | export function decodeStringCompact(data: Uint8Array, pos: number, minor: number, options: DecodeOptions): Token; 9 | /** 10 | * @param {Uint8Array} data 11 | * @param {number} pos 12 | * @param {number} _minor 13 | * @param {DecodeOptions} options 14 | * @returns {Token} 15 | */ 16 | export function decodeString8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 17 | /** 18 | * @param {Uint8Array} data 19 | * @param {number} pos 20 | * @param {number} _minor 21 | * @param {DecodeOptions} options 22 | * @returns {Token} 23 | */ 24 | export function decodeString16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 25 | /** 26 | * @param {Uint8Array} data 27 | * @param {number} pos 28 | * @param {number} _minor 29 | * @param {DecodeOptions} options 30 | * @returns {Token} 31 | */ 32 | export function decodeString32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 33 | /** 34 | * @param {Uint8Array} data 35 | * @param {number} pos 36 | * @param {number} _minor 37 | * @param {DecodeOptions} options 38 | * @returns {Token} 39 | */ 40 | export function decodeString64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 41 | export const encodeString: typeof encodeBytes; 42 | export type Bl = import("./bl.js").Bl; 43 | export type DecodeOptions = import("../interface").DecodeOptions; 44 | import { Token } from './token.js'; 45 | import { encodeBytes } from './2bytes.js'; 46 | //# sourceMappingURL=3string.d.ts.map -------------------------------------------------------------------------------- /types/lib/byte-utils.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array|number[]} buf 3 | * @returns {Uint8Array} 4 | */ 5 | export function asU8A(buf: Uint8Array | number[]): Uint8Array; 6 | /** 7 | * @param {Uint8Array} b1 8 | * @param {Uint8Array} b2 9 | * @returns {number} 10 | */ 11 | export function compare(b1: Uint8Array, b2: Uint8Array): number; 12 | /** 13 | * @param {number[]} codePoints 14 | * @returns {string} 15 | */ 16 | export function decodeCodePointsArray(codePoints: number[]): string; 17 | export const useBuffer: boolean; 18 | /** 19 | * @param {Uint8Array} bytes 20 | * @param {number} start 21 | * @param {number} end 22 | */ 23 | export function toString(bytes: Uint8Array, start: number, end: number): string; 24 | export const fromString: ((string: string) => number[] | Buffer) | ((string: string) => Uint8Array | number[]); 25 | export function fromArray(arr: number[]): Uint8Array; 26 | /** 27 | * @param {Uint8Array} bytes 28 | * @param {number} start 29 | * @param {number} end 30 | */ 31 | export function slice(bytes: Uint8Array, start: number, end: number): Uint8Array; 32 | /** 33 | * @param {Uint8Array[]} chunks 34 | * @param {number} length 35 | * @returns {Uint8Array} 36 | */ 37 | export function concat(chunks: Uint8Array[], length: number): Uint8Array; 38 | /** 39 | * @param {number} size 40 | * @returns {Uint8Array} 41 | */ 42 | export function alloc(size: number): Uint8Array; 43 | /** 44 | * @param {Uint8Array} d 45 | * @returns {string} 46 | */ 47 | export function toHex(d: Uint8Array): string; 48 | /** 49 | * @param {string|Uint8Array} hex 50 | * @returns {Uint8Array} 51 | */ 52 | export function fromHex(hex: string | Uint8Array): Uint8Array; 53 | //# sourceMappingURL=byte-utils.d.ts.map -------------------------------------------------------------------------------- /types/interface.d.ts: -------------------------------------------------------------------------------- 1 | import { Token } from './lib/token'; 2 | import { Bl } from './lib/bl'; 3 | export type TokenOrNestedTokens = Token | Token[] | TokenOrNestedTokens[]; 4 | export interface Reference { 5 | parent: Reference | undefined; 6 | obj: object | any[]; 7 | includes(obj: object | any[]): boolean; 8 | } 9 | export type OptionalTypeEncoder = (data: any, typ: string, options: EncodeOptions, refStack?: Reference) => TokenOrNestedTokens | null; 10 | export type StrictTypeEncoder = (data: any, typ: string, options: EncodeOptions, refStack?: Reference) => TokenOrNestedTokens; 11 | export type TokenTypeEncoder = { 12 | (buf: Bl, token: Token, options?: EncodeOptions): void; 13 | compareTokens(t1: Token, t2: Token): number; 14 | encodedSize?(token: Token, options?: EncodeOptions): number; 15 | }; 16 | export type MapSorter = (e1: (Token | Token[])[], e2: (Token | Token[])[]) => number; 17 | export type QuickEncodeToken = (token: Token) => Uint8Array | undefined; 18 | export interface DecodeTokenizer { 19 | done(): boolean; 20 | next(): Token; 21 | pos(): number; 22 | } 23 | export type TagDecoder = (inner: any) => any; 24 | export interface DecodeOptions { 25 | allowIndefinite?: boolean; 26 | allowUndefined?: boolean; 27 | coerceUndefinedToNull?: boolean; 28 | allowInfinity?: boolean; 29 | allowNaN?: boolean; 30 | allowBigInt?: boolean; 31 | strict?: boolean; 32 | useMaps?: boolean; 33 | rejectDuplicateMapKeys?: boolean; 34 | retainStringBytes?: boolean; 35 | tags?: TagDecoder[]; 36 | tokenizer?: DecodeTokenizer; 37 | } 38 | export interface EncodeOptions { 39 | float64?: boolean; 40 | addBreakTokens?: boolean; 41 | mapSorter?: MapSorter; 42 | quickEncodeToken?: QuickEncodeToken; 43 | typeEncoders?: { 44 | [typeName: string]: OptionalTypeEncoder; 45 | }; 46 | } 47 | //# sourceMappingURL=interface.d.ts.map -------------------------------------------------------------------------------- /interface.ts: -------------------------------------------------------------------------------- 1 | import { Token } from './lib/token' 2 | import { Bl } from './lib/bl' 3 | 4 | export type TokenOrNestedTokens = Token | Token[] | TokenOrNestedTokens[] 5 | 6 | export interface Reference { 7 | parent: Reference | undefined 8 | obj: object | any[] 9 | includes(obj: object | any[]): boolean 10 | } 11 | 12 | export type OptionalTypeEncoder = (data: any, typ: string, options: EncodeOptions, refStack?: Reference) => TokenOrNestedTokens | null 13 | 14 | export type StrictTypeEncoder = (data: any, typ: string, options: EncodeOptions, refStack?: Reference) => TokenOrNestedTokens 15 | 16 | export type TokenTypeEncoder = { 17 | (buf: Bl, token: Token, options?: EncodeOptions): void; 18 | compareTokens(t1: Token, t2: Token): number; 19 | // TODO: make this non-optional as a breaking change and remove the throw in length.js 20 | encodedSize?(token: Token, options?: EncodeOptions): number; 21 | } 22 | 23 | export type MapSorter = (e1: (Token | Token[])[], e2: (Token | Token[])[]) => number 24 | 25 | export type QuickEncodeToken = (token: Token) => Uint8Array | undefined 26 | 27 | export interface DecodeTokenizer { 28 | done(): boolean, 29 | next(): Token, 30 | pos(): number, 31 | } 32 | 33 | export type TagDecoder = (inner: any) => any 34 | 35 | export interface DecodeOptions { 36 | allowIndefinite?: boolean 37 | allowUndefined?: boolean 38 | coerceUndefinedToNull?: boolean 39 | allowInfinity?: boolean 40 | allowNaN?: boolean 41 | allowBigInt?: boolean 42 | strict?: boolean 43 | useMaps?: boolean 44 | rejectDuplicateMapKeys?: boolean 45 | retainStringBytes?: boolean 46 | tags?: TagDecoder[], 47 | tokenizer?: DecodeTokenizer 48 | } 49 | 50 | export interface EncodeOptions { 51 | float64?: boolean, 52 | addBreakTokens?: boolean, 53 | mapSorter?: MapSorter, 54 | quickEncodeToken?: QuickEncodeToken, 55 | typeEncoders?: { [typeName: string]: OptionalTypeEncoder } 56 | } 57 | -------------------------------------------------------------------------------- /types/interface.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AACnC,OAAO,EAAE,EAAE,EAAE,MAAM,UAAU,CAAA;AAE7B,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,KAAK,EAAE,GAAG,mBAAmB,EAAE,CAAA;AAEzE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAA;IAC7B,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,CAAA;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAA;CACvC;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,mBAAmB,GAAG,IAAI,CAAA;AAEtI,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,mBAAmB,CAAA;AAE7H,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACvD,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,CAAC;IAE5C,WAAW,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;CAC7D,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,EAAE,KAAK,MAAM,CAAA;AAEpF,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,UAAU,GAAG,SAAS,CAAA;AAEvE,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,OAAO,CAAC;IAChB,IAAI,IAAI,KAAK,CAAC;IACd,GAAG,IAAI,MAAM,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;AAE5C,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,YAAY,CAAC,EAAE;QAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAAA;KAAE,CAAA;CAC3D"} -------------------------------------------------------------------------------- /types/lib/1negint.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./bl.js').Bl} Bl 3 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 4 | */ 5 | /** 6 | * @param {Uint8Array} data 7 | * @param {number} pos 8 | * @param {number} _minor 9 | * @param {DecodeOptions} options 10 | * @returns {Token} 11 | */ 12 | export function decodeNegint8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 13 | /** 14 | * @param {Uint8Array} data 15 | * @param {number} pos 16 | * @param {number} _minor 17 | * @param {DecodeOptions} options 18 | * @returns {Token} 19 | */ 20 | export function decodeNegint16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 21 | /** 22 | * @param {Uint8Array} data 23 | * @param {number} pos 24 | * @param {number} _minor 25 | * @param {DecodeOptions} options 26 | * @returns {Token} 27 | */ 28 | export function decodeNegint32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 29 | /** 30 | * @param {Uint8Array} data 31 | * @param {number} pos 32 | * @param {number} _minor 33 | * @param {DecodeOptions} options 34 | * @returns {Token} 35 | */ 36 | export function decodeNegint64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 37 | /** 38 | * @param {Bl} buf 39 | * @param {Token} token 40 | */ 41 | export function encodeNegint(buf: Bl, token: Token): void; 42 | export namespace encodeNegint { 43 | /** 44 | * @param {Token} token 45 | * @returns {number} 46 | */ 47 | function encodedSize(token: Token): number; 48 | /** 49 | * @param {Token} tok1 50 | * @param {Token} tok2 51 | * @returns {number} 52 | */ 53 | function compareTokens(tok1: Token, tok2: Token): number; 54 | } 55 | export type Bl = import("./bl.js").Bl; 56 | export type DecodeOptions = import("../interface").DecodeOptions; 57 | import { Token } from './token.js'; 58 | //# sourceMappingURL=1negint.d.ts.map -------------------------------------------------------------------------------- /types/lib/json/decode.d.ts: -------------------------------------------------------------------------------- 1 | export type DecodeOptions = import("../../interface").DecodeOptions; 2 | export type DecodeTokenizer = import("../../interface").DecodeTokenizer; 3 | /** 4 | * @param {Uint8Array} data 5 | * @param {DecodeOptions} [options] 6 | * @returns {any} 7 | */ 8 | export function decode(data: Uint8Array, options?: DecodeOptions): any; 9 | /** 10 | * @param {Uint8Array} data 11 | * @param {DecodeOptions} [options] 12 | * @returns {[any, Uint8Array]} 13 | */ 14 | export function decodeFirst(data: Uint8Array, options?: DecodeOptions): [any, Uint8Array]; 15 | /** 16 | * @typedef {import('../../interface').DecodeOptions} DecodeOptions 17 | * @typedef {import('../../interface').DecodeTokenizer} DecodeTokenizer 18 | */ 19 | /** 20 | * @implements {DecodeTokenizer} 21 | */ 22 | export class Tokenizer implements DecodeTokenizer { 23 | /** 24 | * @param {Uint8Array} data 25 | * @param {DecodeOptions} options 26 | */ 27 | constructor(data: Uint8Array, options?: DecodeOptions); 28 | _pos: number; 29 | data: Uint8Array; 30 | options: import("../../interface").DecodeOptions; 31 | /** @type {string[]} */ 32 | modeStack: string[]; 33 | lastToken: string; 34 | pos(): number; 35 | /** 36 | * @returns {boolean} 37 | */ 38 | done(): boolean; 39 | /** 40 | * @returns {number} 41 | */ 42 | ch(): number; 43 | /** 44 | * @returns {string} 45 | */ 46 | currentMode(): string; 47 | skipWhitespace(): void; 48 | /** 49 | * @param {number[]} str 50 | */ 51 | expect(str: number[]): void; 52 | parseNumber(): Token; 53 | /** 54 | * @returns {Token} 55 | */ 56 | parseString(): Token; 57 | /** 58 | * @returns {Token} 59 | */ 60 | parseValue(): Token; 61 | /** 62 | * @returns {Token} 63 | */ 64 | next(): Token; 65 | } 66 | import { Token } from '../token.js'; 67 | //# sourceMappingURL=decode.d.ts.map -------------------------------------------------------------------------------- /lib/token.js: -------------------------------------------------------------------------------- 1 | class Type { 2 | /** 3 | * @param {number} major 4 | * @param {string} name 5 | * @param {boolean} terminal 6 | */ 7 | constructor (major, name, terminal) { 8 | this.major = major 9 | this.majorEncoded = major << 5 10 | this.name = name 11 | this.terminal = terminal 12 | } 13 | 14 | /* c8 ignore next 3 */ 15 | toString () { 16 | return `Type[${this.major}].${this.name}` 17 | } 18 | 19 | /** 20 | * @param {Type} typ 21 | * @returns {number} 22 | */ 23 | compare (typ) { 24 | /* c8 ignore next 1 */ 25 | return this.major < typ.major ? -1 : this.major > typ.major ? 1 : 0 26 | } 27 | } 28 | 29 | // convert to static fields when better supported 30 | Type.uint = new Type(0, 'uint', true) 31 | Type.negint = new Type(1, 'negint', true) 32 | Type.bytes = new Type(2, 'bytes', true) 33 | Type.string = new Type(3, 'string', true) 34 | Type.array = new Type(4, 'array', false) 35 | Type.map = new Type(5, 'map', false) 36 | Type.tag = new Type(6, 'tag', false) // terminal? 37 | Type.float = new Type(7, 'float', true) 38 | Type.false = new Type(7, 'false', true) 39 | Type.true = new Type(7, 'true', true) 40 | Type.null = new Type(7, 'null', true) 41 | Type.undefined = new Type(7, 'undefined', true) 42 | Type.break = new Type(7, 'break', true) 43 | // Type.indefiniteLength = new Type(0, 'indefiniteLength', true) 44 | 45 | class Token { 46 | /** 47 | * @param {Type} type 48 | * @param {any} [value] 49 | * @param {number} [encodedLength] 50 | */ 51 | constructor (type, value, encodedLength) { 52 | this.type = type 53 | this.value = value 54 | this.encodedLength = encodedLength 55 | /** @type {Uint8Array|undefined} */ 56 | this.encodedBytes = undefined 57 | /** @type {Uint8Array|undefined} */ 58 | this.byteValue = undefined 59 | } 60 | 61 | /* c8 ignore next 3 */ 62 | toString () { 63 | return `Token[${this.type}].${this.value}` 64 | } 65 | } 66 | 67 | export { Type, Token } 68 | -------------------------------------------------------------------------------- /types/lib/encode.d.ts: -------------------------------------------------------------------------------- 1 | /** @returns {TokenTypeEncoder[]} */ 2 | export function makeCborEncoders(): TokenTypeEncoder[]; 3 | export type EncodeOptions = import("../interface").EncodeOptions; 4 | export type OptionalTypeEncoder = import("../interface").OptionalTypeEncoder; 5 | export type Reference = import("../interface").Reference; 6 | export type StrictTypeEncoder = import("../interface").StrictTypeEncoder; 7 | export type TokenTypeEncoder = import("../interface").TokenTypeEncoder; 8 | export type TokenOrNestedTokens = import("../interface").TokenOrNestedTokens; 9 | /** 10 | * @param {any} obj 11 | * @param {EncodeOptions} [options] 12 | * @param {Reference} [refStack] 13 | * @returns {TokenOrNestedTokens} 14 | */ 15 | export function objectToTokens(obj: any, options?: EncodeOptions, refStack?: Reference): TokenOrNestedTokens; 16 | /** 17 | * @param {any} data 18 | * @param {EncodeOptions} [options] 19 | * @returns {Uint8Array} 20 | */ 21 | export function encode(data: any, options?: EncodeOptions): Uint8Array; 22 | /** 23 | * @param {any} data 24 | * @param {TokenTypeEncoder[]} encoders 25 | * @param {EncodeOptions} options 26 | * @returns {Uint8Array} 27 | */ 28 | export function encodeCustom(data: any, encoders: TokenTypeEncoder[], options: EncodeOptions): Uint8Array; 29 | /** @implements {Reference} */ 30 | export class Ref implements Reference { 31 | /** 32 | * @param {Reference|undefined} stack 33 | * @param {object|any[]} obj 34 | * @returns {Reference} 35 | */ 36 | static createCheck(stack: Reference | undefined, obj: object | any[]): Reference; 37 | /** 38 | * @param {object|any[]} obj 39 | * @param {Reference|undefined} parent 40 | */ 41 | constructor(obj: object | any[], parent: Reference | undefined); 42 | obj: object | any[]; 43 | parent: import("../interface").Reference | undefined; 44 | /** 45 | * @param {object|any[]} obj 46 | * @returns {boolean} 47 | */ 48 | includes(obj: object | any[]): boolean; 49 | } 50 | //# sourceMappingURL=encode.d.ts.map -------------------------------------------------------------------------------- /test/test-length.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | import { garbage } from 'ipld-garbage' 5 | import { uintBoundaries } from '../lib/0uint.js' 6 | import { encode } from '../cborg.js' 7 | import { encodedLength } from '../lib/length.js' 8 | import { dateEncoder } from './common.js' 9 | 10 | const { assert } = chai 11 | 12 | function verifyLength (object, options) { 13 | const len = encodedLength(object, options) 14 | const encoded = encode(object, options) 15 | const actual = encoded.length 16 | assert.strictEqual(actual, len, JSON.stringify(object)) 17 | } 18 | 19 | describe('encodedLength', () => { 20 | it('int boundaries', () => { 21 | for (let ii = 0; ii < 4; ii++) { 22 | verifyLength(uintBoundaries[ii]) 23 | verifyLength(uintBoundaries[ii] - 1) 24 | verifyLength(uintBoundaries[ii] + 1) 25 | verifyLength(-1 * uintBoundaries[ii]) 26 | verifyLength(-1 * uintBoundaries[ii] - 1) 27 | verifyLength(-1 * uintBoundaries[ii] + 1) 28 | } 29 | }) 30 | 31 | it('tags', () => { 32 | verifyLength({ date: new Date('2013-03-21T20:04:00Z') }, { typeEncoders: { Date: dateEncoder } }) 33 | }) 34 | 35 | it('floats', () => { 36 | verifyLength(0.5) 37 | verifyLength(0.5, { float64: true }) 38 | verifyLength(8.940696716308594e-08) 39 | verifyLength(8.940696716308594e-08, { float64: true }) 40 | }) 41 | 42 | it('small garbage', function () { 43 | this.timeout(10000) 44 | for (let ii = 0; ii < 1000; ii++) { 45 | const gbg = garbage(1 << 6, { weights: { CID: 0 } }) 46 | verifyLength(gbg) 47 | } 48 | }) 49 | 50 | it('medium garbage', function () { 51 | this.timeout(10000) 52 | for (let ii = 0; ii < 100; ii++) { 53 | const gbg = garbage(1 << 16, { weights: { CID: 0 } }) 54 | verifyLength(gbg) 55 | } 56 | }) 57 | 58 | it('large garbage', function () { 59 | this.timeout(10000) 60 | for (let ii = 0; ii < 10; ii++) { 61 | const gbg = garbage(1 << 20, { weights: { CID: 0 } }) 62 | verifyLength(gbg) 63 | } 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /types/lib/6tag.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./bl.js').Bl} Bl 3 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 4 | */ 5 | /** 6 | * @param {Uint8Array} _data 7 | * @param {number} _pos 8 | * @param {number} minor 9 | * @param {DecodeOptions} _options 10 | * @returns {Token} 11 | */ 12 | export function decodeTagCompact(_data: Uint8Array, _pos: number, minor: number, _options: DecodeOptions): Token; 13 | /** 14 | * @param {Uint8Array} data 15 | * @param {number} pos 16 | * @param {number} _minor 17 | * @param {DecodeOptions} options 18 | * @returns {Token} 19 | */ 20 | export function decodeTag8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 21 | /** 22 | * @param {Uint8Array} data 23 | * @param {number} pos 24 | * @param {number} _minor 25 | * @param {DecodeOptions} options 26 | * @returns {Token} 27 | */ 28 | export function decodeTag16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 29 | /** 30 | * @param {Uint8Array} data 31 | * @param {number} pos 32 | * @param {number} _minor 33 | * @param {DecodeOptions} options 34 | * @returns {Token} 35 | */ 36 | export function decodeTag32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 37 | /** 38 | * @param {Uint8Array} data 39 | * @param {number} pos 40 | * @param {number} _minor 41 | * @param {DecodeOptions} options 42 | * @returns {Token} 43 | */ 44 | export function decodeTag64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 45 | /** 46 | * @param {Bl} buf 47 | * @param {Token} token 48 | */ 49 | export function encodeTag(buf: Bl, token: Token): void; 50 | export namespace encodeTag { 51 | let compareTokens: (tok1: Token, tok2: Token) => number; 52 | /** 53 | * @param {Token} token 54 | * @returns {number} 55 | */ 56 | function encodedSize(token: Token): number; 57 | } 58 | export type Bl = import("./bl.js").Bl; 59 | export type DecodeOptions = import("../interface").DecodeOptions; 60 | import { Token } from './token.js'; 61 | //# sourceMappingURL=6tag.d.ts.map -------------------------------------------------------------------------------- /types/lib/7float.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} _data 3 | * @param {number} _pos 4 | * @param {number} _minor 5 | * @param {DecodeOptions} options 6 | * @returns {Token} 7 | */ 8 | export function decodeUndefined(_data: Uint8Array, _pos: number, _minor: number, options: DecodeOptions): Token; 9 | /** 10 | * @param {Uint8Array} _data 11 | * @param {number} _pos 12 | * @param {number} _minor 13 | * @param {DecodeOptions} options 14 | * @returns {Token} 15 | */ 16 | export function decodeBreak(_data: Uint8Array, _pos: number, _minor: number, options: DecodeOptions): Token; 17 | /** 18 | * @param {Uint8Array} data 19 | * @param {number} pos 20 | * @param {number} _minor 21 | * @param {DecodeOptions} options 22 | * @returns {Token} 23 | */ 24 | export function decodeFloat16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 25 | /** 26 | * @param {Uint8Array} data 27 | * @param {number} pos 28 | * @param {number} _minor 29 | * @param {DecodeOptions} options 30 | * @returns {Token} 31 | */ 32 | export function decodeFloat32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 33 | /** 34 | * @param {Uint8Array} data 35 | * @param {number} pos 36 | * @param {number} _minor 37 | * @param {DecodeOptions} options 38 | * @returns {Token} 39 | */ 40 | export function decodeFloat64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 41 | /** 42 | * @param {Bl} buf 43 | * @param {Token} token 44 | * @param {EncodeOptions} options 45 | */ 46 | export function encodeFloat(buf: Bl, token: Token, options: EncodeOptions): void; 47 | export namespace encodeFloat { 48 | /** 49 | * @param {Token} token 50 | * @param {EncodeOptions} options 51 | * @returns {number} 52 | */ 53 | function encodedSize(token: Token, options: EncodeOptions): number; 54 | let compareTokens: (tok1: Token, tok2: Token) => number; 55 | } 56 | export type Bl = import("./bl.js").Bl; 57 | export type DecodeOptions = import("../interface").DecodeOptions; 58 | export type EncodeOptions = import("../interface").EncodeOptions; 59 | import { Token } from './token.js'; 60 | //# sourceMappingURL=7float.d.ts.map -------------------------------------------------------------------------------- /types/lib/5map.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} data 3 | * @param {number} pos 4 | * @param {number} minor 5 | * @param {DecodeOptions} _options 6 | * @returns {Token} 7 | */ 8 | export function decodeMapCompact(data: Uint8Array, pos: number, minor: number, _options: DecodeOptions): Token; 9 | /** 10 | * @param {Uint8Array} data 11 | * @param {number} pos 12 | * @param {number} _minor 13 | * @param {DecodeOptions} options 14 | * @returns {Token} 15 | */ 16 | export function decodeMap8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 17 | /** 18 | * @param {Uint8Array} data 19 | * @param {number} pos 20 | * @param {number} _minor 21 | * @param {DecodeOptions} options 22 | * @returns {Token} 23 | */ 24 | export function decodeMap16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 25 | /** 26 | * @param {Uint8Array} data 27 | * @param {number} pos 28 | * @param {number} _minor 29 | * @param {DecodeOptions} options 30 | * @returns {Token} 31 | */ 32 | export function decodeMap32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 33 | /** 34 | * @param {Uint8Array} data 35 | * @param {number} pos 36 | * @param {number} _minor 37 | * @param {DecodeOptions} options 38 | * @returns {Token} 39 | */ 40 | export function decodeMap64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 41 | /** 42 | * @param {Uint8Array} data 43 | * @param {number} pos 44 | * @param {number} _minor 45 | * @param {DecodeOptions} options 46 | * @returns {Token} 47 | */ 48 | export function decodeMapIndefinite(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 49 | /** 50 | * @param {Bl} buf 51 | * @param {Token} token 52 | */ 53 | export function encodeMap(buf: Bl, token: Token): void; 54 | export namespace encodeMap { 55 | let compareTokens: (tok1: Token, tok2: Token) => number; 56 | /** 57 | * @param {Token} token 58 | * @returns {number} 59 | */ 60 | function encodedSize(token: Token): number; 61 | } 62 | export type Bl = import("./bl.js").Bl; 63 | export type DecodeOptions = import("../interface").DecodeOptions; 64 | import { Token } from './token.js'; 65 | //# sourceMappingURL=5map.d.ts.map -------------------------------------------------------------------------------- /lib/6tag.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './token.js' 2 | import * as uint from './0uint.js' 3 | 4 | /** 5 | * @typedef {import('./bl.js').Bl} Bl 6 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 7 | */ 8 | 9 | /** 10 | * @param {Uint8Array} _data 11 | * @param {number} _pos 12 | * @param {number} minor 13 | * @param {DecodeOptions} _options 14 | * @returns {Token} 15 | */ 16 | export function decodeTagCompact (_data, _pos, minor, _options) { 17 | return new Token(Type.tag, minor, 1) 18 | } 19 | 20 | /** 21 | * @param {Uint8Array} data 22 | * @param {number} pos 23 | * @param {number} _minor 24 | * @param {DecodeOptions} options 25 | * @returns {Token} 26 | */ 27 | export function decodeTag8 (data, pos, _minor, options) { 28 | return new Token(Type.tag, uint.readUint8(data, pos + 1, options), 2) 29 | } 30 | 31 | /** 32 | * @param {Uint8Array} data 33 | * @param {number} pos 34 | * @param {number} _minor 35 | * @param {DecodeOptions} options 36 | * @returns {Token} 37 | */ 38 | export function decodeTag16 (data, pos, _minor, options) { 39 | return new Token(Type.tag, uint.readUint16(data, pos + 1, options), 3) 40 | } 41 | 42 | /** 43 | * @param {Uint8Array} data 44 | * @param {number} pos 45 | * @param {number} _minor 46 | * @param {DecodeOptions} options 47 | * @returns {Token} 48 | */ 49 | export function decodeTag32 (data, pos, _minor, options) { 50 | return new Token(Type.tag, uint.readUint32(data, pos + 1, options), 5) 51 | } 52 | 53 | /** 54 | * @param {Uint8Array} data 55 | * @param {number} pos 56 | * @param {number} _minor 57 | * @param {DecodeOptions} options 58 | * @returns {Token} 59 | */ 60 | export function decodeTag64 (data, pos, _minor, options) { 61 | return new Token(Type.tag, uint.readUint64(data, pos + 1, options), 9) 62 | } 63 | 64 | /** 65 | * @param {Bl} buf 66 | * @param {Token} token 67 | */ 68 | export function encodeTag (buf, token) { 69 | uint.encodeUintValue(buf, Type.tag.majorEncoded, token.value) 70 | } 71 | 72 | encodeTag.compareTokens = uint.encodeUint.compareTokens 73 | 74 | /** 75 | * @param {Token} token 76 | * @returns {number} 77 | */ 78 | encodeTag.encodedSize = function encodedSize (token) { 79 | return uint.encodeUintValue.encodedSize(token.value) 80 | } 81 | -------------------------------------------------------------------------------- /types/lib/4array.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} data 3 | * @param {number} pos 4 | * @param {number} minor 5 | * @param {DecodeOptions} _options 6 | * @returns {Token} 7 | */ 8 | export function decodeArrayCompact(data: Uint8Array, pos: number, minor: number, _options: DecodeOptions): Token; 9 | /** 10 | * @param {Uint8Array} data 11 | * @param {number} pos 12 | * @param {number} _minor 13 | * @param {DecodeOptions} options 14 | * @returns {Token} 15 | */ 16 | export function decodeArray8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 17 | /** 18 | * @param {Uint8Array} data 19 | * @param {number} pos 20 | * @param {number} _minor 21 | * @param {DecodeOptions} options 22 | * @returns {Token} 23 | */ 24 | export function decodeArray16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 25 | /** 26 | * @param {Uint8Array} data 27 | * @param {number} pos 28 | * @param {number} _minor 29 | * @param {DecodeOptions} options 30 | * @returns {Token} 31 | */ 32 | export function decodeArray32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 33 | /** 34 | * @param {Uint8Array} data 35 | * @param {number} pos 36 | * @param {number} _minor 37 | * @param {DecodeOptions} options 38 | * @returns {Token} 39 | */ 40 | export function decodeArray64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 41 | /** 42 | * @param {Uint8Array} data 43 | * @param {number} pos 44 | * @param {number} _minor 45 | * @param {DecodeOptions} options 46 | * @returns {Token} 47 | */ 48 | export function decodeArrayIndefinite(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 49 | /** 50 | * @param {Bl} buf 51 | * @param {Token} token 52 | */ 53 | export function encodeArray(buf: Bl, token: Token): void; 54 | export namespace encodeArray { 55 | let compareTokens: (tok1: Token, tok2: Token) => number; 56 | /** 57 | * @param {Token} token 58 | * @returns {number} 59 | */ 60 | function encodedSize(token: Token): number; 61 | } 62 | export type Bl = import("./bl.js").Bl; 63 | export type DecodeOptions = import("../interface").DecodeOptions; 64 | import { Token } from './token.js'; 65 | //# sourceMappingURL=4array.d.ts.map -------------------------------------------------------------------------------- /types/lib/2bytes.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Uint8Array} data 3 | * @param {number} pos 4 | * @param {number} minor 5 | * @param {DecodeOptions} _options 6 | * @returns {Token} 7 | */ 8 | export function decodeBytesCompact(data: Uint8Array, pos: number, minor: number, _options: DecodeOptions): Token; 9 | /** 10 | * @param {Uint8Array} data 11 | * @param {number} pos 12 | * @param {number} _minor 13 | * @param {DecodeOptions} options 14 | * @returns {Token} 15 | */ 16 | export function decodeBytes8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 17 | /** 18 | * @param {Uint8Array} data 19 | * @param {number} pos 20 | * @param {number} _minor 21 | * @param {DecodeOptions} options 22 | * @returns {Token} 23 | */ 24 | export function decodeBytes16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 25 | /** 26 | * @param {Uint8Array} data 27 | * @param {number} pos 28 | * @param {number} _minor 29 | * @param {DecodeOptions} options 30 | * @returns {Token} 31 | */ 32 | export function decodeBytes32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 33 | /** 34 | * @param {Uint8Array} data 35 | * @param {number} pos 36 | * @param {number} _minor 37 | * @param {DecodeOptions} options 38 | * @returns {Token} 39 | */ 40 | export function decodeBytes64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 41 | /** 42 | * @param {Bl} buf 43 | * @param {Token} token 44 | */ 45 | export function encodeBytes(buf: Bl, token: Token): void; 46 | export namespace encodeBytes { 47 | /** 48 | * @param {Token} token 49 | * @returns {number} 50 | */ 51 | function encodedSize(token: Token): number; 52 | /** 53 | * @param {Token} tok1 54 | * @param {Token} tok2 55 | * @returns {number} 56 | */ 57 | function compareTokens(tok1: Token, tok2: Token): number; 58 | } 59 | /** 60 | * @param {Uint8Array} b1 61 | * @param {Uint8Array} b2 62 | * @returns {number} 63 | */ 64 | export function compareBytes(b1: Uint8Array, b2: Uint8Array): number; 65 | export type Bl = import("./bl.js").Bl; 66 | export type DecodeOptions = import("../interface").DecodeOptions; 67 | import { Token } from './token.js'; 68 | //# sourceMappingURL=2bytes.d.ts.map -------------------------------------------------------------------------------- /taglib.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './cborg.js' 2 | 3 | /* 4 | A collection of some standard CBOR tags. 5 | 6 | There are no tags included by default in the cborg encoder or decoder, you have 7 | to include them by passing options. `typeEncoders` for encode() and `tags` for 8 | decode(). 9 | 10 | The encoders here can be included with these options (see the tests for how this 11 | can be done), or as examples for writing additional tags. Additional standard 12 | (and reasonable) tags may be added here by pull request. 13 | */ 14 | 15 | /* TAG(2) Bignums https://tools.ietf.org/html/rfc8949#section-3.4.3 */ 16 | const neg1b = BigInt(-1) 17 | const pos1b = BigInt(1) 18 | const zerob = BigInt(0) 19 | // const twob = BigInt(2) 20 | const eightb = BigInt(8) 21 | 22 | /** 23 | * @param {Uint8Array} bytes 24 | * @returns {bigint} 25 | */ 26 | export function bigIntDecoder (bytes) { 27 | // TODO: assert that `bytes` is a `Uint8Array` 28 | let bi = zerob 29 | for (let ii = 0; ii < bytes.length; ii++) { 30 | bi = (bi << eightb) + BigInt(bytes[ii]) 31 | } 32 | return bi 33 | } 34 | 35 | /** 36 | * @param {bigint} bi 37 | * @returns {Uint8Array} 38 | */ 39 | function fromBigInt (bi) { 40 | const buf = [] 41 | while (bi > 0) { 42 | buf.unshift(Number(bi) & 0xff) 43 | bi >>= eightb 44 | } 45 | return Uint8Array.from(buf) 46 | } 47 | 48 | // assuming that we're receiving a BigInt here, it should be registered for 49 | // type 'bigint' for this to work. 50 | const maxSafeBigInt = BigInt('18446744073709551615') // (twob ** BigInt(64)) - pos1b 51 | const minSafeBigInt = BigInt('-18446744073709551616') // neg1b * (twob ** BigInt(64)) 52 | /** 53 | * @param {bigint} obj 54 | * @returns {Token[]|null} 55 | */ 56 | export function bigIntEncoder (obj) { 57 | if (obj >= minSafeBigInt && obj <= maxSafeBigInt) { 58 | return null // null = do it the standard way 59 | } 60 | return [ 61 | new Token(Type.tag, obj >= zerob ? 2 : 3), 62 | new Token(Type.bytes, fromBigInt(obj >= zerob ? obj : obj * neg1b - pos1b)) 63 | ] 64 | } 65 | 66 | /** 67 | * TAG(3) Negative Bignums https://tools.ietf.org/html/rfc8949#section-3.4.3 68 | * @param {Uint8Array} bytes 69 | * @returns {bigint} 70 | */ 71 | export function bigNegIntDecoder (bytes) { 72 | return neg1b - bigIntDecoder(bytes) 73 | } 74 | -------------------------------------------------------------------------------- /lib/length.js: -------------------------------------------------------------------------------- 1 | import { makeCborEncoders, objectToTokens } from './encode.js' 2 | import { quickEncodeToken } from './jump.js' 3 | 4 | /** 5 | * @typedef {import('../interface').EncodeOptions} EncodeOptions 6 | * @typedef {import('../interface').TokenTypeEncoder} TokenTypeEncoder 7 | * @typedef {import('../interface').TokenOrNestedTokens} TokenOrNestedTokens 8 | */ 9 | 10 | const cborEncoders = makeCborEncoders() 11 | 12 | /** @type {EncodeOptions} */ 13 | const defaultEncodeOptions = { 14 | float64: false, 15 | quickEncodeToken 16 | } 17 | 18 | /** 19 | * Calculate the byte length of the given data when encoded as CBOR with the 20 | * options provided. 21 | * This calculation will be accurate if the same options are used as when 22 | * performing a normal encode. Some encode options can change the encoding 23 | * output length. 24 | * 25 | * @param {any} data 26 | * @param {EncodeOptions} [options] 27 | * @returns {number} 28 | */ 29 | export function encodedLength (data, options) { 30 | options = Object.assign({}, defaultEncodeOptions, options) 31 | options.mapSorter = undefined // won't change the length 32 | const tokens = objectToTokens(data, options) 33 | return tokensToLength(tokens, cborEncoders, options) 34 | } 35 | 36 | /** 37 | * Calculate the byte length of the data as represented by the given tokens when 38 | * encoded as CBOR with the options provided. 39 | * This function is for advanced users and would not normally be called 40 | * directly. See `encodedLength()` for appropriate use. 41 | * 42 | * @param {TokenOrNestedTokens} tokens 43 | * @param {TokenTypeEncoder[]} [encoders] 44 | * @param {EncodeOptions} [options] 45 | */ 46 | export function tokensToLength (tokens, encoders = cborEncoders, options = defaultEncodeOptions) { 47 | if (Array.isArray(tokens)) { 48 | let len = 0 49 | for (const token of tokens) { 50 | len += tokensToLength(token, encoders, options) 51 | } 52 | return len 53 | } else { 54 | const encoder = encoders[tokens.type.major] 55 | /* c8 ignore next 3 */ 56 | if (encoder.encodedSize === undefined || typeof encoder.encodedSize !== 'function') { 57 | throw new Error(`Encoder for ${tokens.type.name} does not have an encodedSize()`) 58 | } 59 | return encoder.encodedSize(tokens, options) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/test-decode-errors.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode } from '../cborg.js' 6 | import { fromHex } from '../lib/byte-utils.js' 7 | 8 | const { assert } = chai 9 | 10 | describe('decode errors', () => { 11 | it('not Uint8Array', () => { 12 | for (const arg of [true, false, null, undefined, 'string', { obj: 'ect' }, {}, ['array'], [], [1, 2, 3], 0, 100, 1.1, -1, Symbol.for('nope')]) { 13 | assert.throws(() => decode(arg), /CBOR decode error.*must be a Uint8Array/) 14 | } 15 | }) 16 | 17 | it('no data', () => { 18 | assert.throws(() => decode(new Uint8Array('')), /CBOR decode error.*content/) 19 | }) 20 | 21 | it('break only', () => { 22 | assert.throws(() => decode(new Uint8Array([255])), /CBOR decode error.*break/) 23 | }) 24 | 25 | it('not enough map entries (value)', () => { 26 | // last value missing 27 | assert.throws(() => decode(fromHex('a2616f016174')), /map.*not enough entries.*value/) 28 | }) 29 | 30 | it('not enough map entries (key)', () => { 31 | // last key & value missing 32 | assert.throws(() => decode(fromHex('a2616f01')), /map.*not enough entries.*key/) 33 | }) 34 | 35 | it('break in lengthed map', () => { 36 | // 0xff (break) in the middle 37 | assert.throws(() => decode(fromHex('a2616f01ff740f')), /unexpected break to lengthed map/) 38 | }) 39 | 40 | it('not enough array entries', () => { 41 | // last value missing 42 | assert.throws(() => decode(fromHex('82616f')), /array.*not enough entries/) 43 | }) 44 | 45 | it('break in lengthed array', () => { 46 | // last value missing 47 | assert.throws(() => decode(fromHex('82ff')), /unexpected break to lengthed array/) 48 | }) 49 | 50 | it('no such decoder', () => { 51 | // last value missing 52 | assert.throws(() => decode(fromHex('82ff')), /unexpected break to lengthed array/) 53 | }) 54 | 55 | it('too many terminals', () => { 56 | // two '1's 57 | assert.throws(() => decode(fromHex('0101')), /too many terminals/) 58 | }) 59 | 60 | it('rejectDuplicateMapKeys enabled on duplicate keys', () => { 61 | assert.deepStrictEqual(decode(fromHex('a3636261720363666f6f0163666f6f02')), { foo: 2, bar: 3 }) 62 | assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), { rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/) 63 | assert.throws(() => decode(fromHex('a3636261720363666f6f0163666f6f02'), { useMaps: true, rejectDuplicateMapKeys: true }), /CBOR decode error: found repeat map key "foo"/) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /lib/is.js: -------------------------------------------------------------------------------- 1 | // This is an unfortunate replacement for @sindresorhus/is that we need to 2 | // re-implement for performance purposes. In particular the is.observable() 3 | // check is expensive, and unnecessary for our purposes. The values returned 4 | // are compatible with @sindresorhus/is, however. 5 | 6 | const typeofs = [ 7 | 'string', 8 | 'number', 9 | 'bigint', 10 | 'symbol' 11 | ] 12 | 13 | const objectTypeNames = [ 14 | 'Function', 15 | 'Generator', 16 | 'AsyncGenerator', 17 | 'GeneratorFunction', 18 | 'AsyncGeneratorFunction', 19 | 'AsyncFunction', 20 | 'Observable', 21 | 'Array', 22 | 'Buffer', 23 | 'Object', 24 | 'RegExp', 25 | 'Date', 26 | 'Error', 27 | 'Map', 28 | 'Set', 29 | 'WeakMap', 30 | 'WeakSet', 31 | 'ArrayBuffer', 32 | 'SharedArrayBuffer', 33 | 'DataView', 34 | 'Promise', 35 | 'URL', 36 | 'HTMLElement', 37 | 'Int8Array', 38 | 'Uint8Array', 39 | 'Uint8ClampedArray', 40 | 'Int16Array', 41 | 'Uint16Array', 42 | 'Int32Array', 43 | 'Uint32Array', 44 | 'Float32Array', 45 | 'Float64Array', 46 | 'BigInt64Array', 47 | 'BigUint64Array' 48 | ] 49 | 50 | /** 51 | * @param {any} value 52 | * @returns {string} 53 | */ 54 | export function is (value) { 55 | if (value === null) { 56 | return 'null' 57 | } 58 | if (value === undefined) { 59 | return 'undefined' 60 | } 61 | if (value === true || value === false) { 62 | return 'boolean' 63 | } 64 | const typeOf = typeof value 65 | if (typeofs.includes(typeOf)) { 66 | return typeOf 67 | } 68 | /* c8 ignore next 4 */ 69 | // not going to bother testing this, it's not going to be valid anyway 70 | if (typeOf === 'function') { 71 | return 'Function' 72 | } 73 | if (Array.isArray(value)) { 74 | return 'Array' 75 | } 76 | if (isBuffer(value)) { 77 | return 'Buffer' 78 | } 79 | const objectType = getObjectType(value) 80 | if (objectType) { 81 | return objectType 82 | } 83 | /* c8 ignore next */ 84 | return 'Object' 85 | } 86 | 87 | /** 88 | * @param {any} value 89 | * @returns {boolean} 90 | */ 91 | function isBuffer (value) { 92 | return value && value.constructor && value.constructor.isBuffer && value.constructor.isBuffer.call(null, value) 93 | } 94 | 95 | /** 96 | * @param {any} value 97 | * @returns {string|undefined} 98 | */ 99 | function getObjectType (value) { 100 | const objectTypeName = Object.prototype.toString.call(value).slice(8, -1) 101 | if (objectTypeNames.includes(objectTypeName)) { 102 | return objectTypeName 103 | } 104 | /* c8 ignore next */ 105 | return undefined 106 | } 107 | -------------------------------------------------------------------------------- /lib/3string.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './token.js' 2 | import { assertEnoughData, decodeErrPrefix } from './common.js' 3 | import * as uint from './0uint.js' 4 | import { encodeBytes } from './2bytes.js' 5 | import { toString, slice } from './byte-utils.js' 6 | 7 | /** 8 | * @typedef {import('./bl.js').Bl} Bl 9 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 10 | */ 11 | 12 | /** 13 | * @param {Uint8Array} data 14 | * @param {number} pos 15 | * @param {number} prefix 16 | * @param {number} length 17 | * @param {DecodeOptions} options 18 | * @returns {Token} 19 | */ 20 | function toToken (data, pos, prefix, length, options) { 21 | const totLength = prefix + length 22 | assertEnoughData(data, pos, totLength) 23 | const tok = new Token(Type.string, toString(data, pos + prefix, pos + totLength), totLength) 24 | if (options.retainStringBytes === true) { 25 | tok.byteValue = slice(data, pos + prefix, pos + totLength) 26 | } 27 | return tok 28 | } 29 | 30 | /** 31 | * @param {Uint8Array} data 32 | * @param {number} pos 33 | * @param {number} minor 34 | * @param {DecodeOptions} options 35 | * @returns {Token} 36 | */ 37 | export function decodeStringCompact (data, pos, minor, options) { 38 | return toToken(data, pos, 1, minor, options) 39 | } 40 | 41 | /** 42 | * @param {Uint8Array} data 43 | * @param {number} pos 44 | * @param {number} _minor 45 | * @param {DecodeOptions} options 46 | * @returns {Token} 47 | */ 48 | export function decodeString8 (data, pos, _minor, options) { 49 | return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options), options) 50 | } 51 | 52 | /** 53 | * @param {Uint8Array} data 54 | * @param {number} pos 55 | * @param {number} _minor 56 | * @param {DecodeOptions} options 57 | * @returns {Token} 58 | */ 59 | export function decodeString16 (data, pos, _minor, options) { 60 | return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options), options) 61 | } 62 | 63 | /** 64 | * @param {Uint8Array} data 65 | * @param {number} pos 66 | * @param {number} _minor 67 | * @param {DecodeOptions} options 68 | * @returns {Token} 69 | */ 70 | export function decodeString32 (data, pos, _minor, options) { 71 | return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options), options) 72 | } 73 | 74 | // TODO: maybe we shouldn't support this .. 75 | /** 76 | * @param {Uint8Array} data 77 | * @param {number} pos 78 | * @param {number} _minor 79 | * @param {DecodeOptions} options 80 | * @returns {Token} 81 | */ 82 | export function decodeString64 (data, pos, _minor, options) { 83 | const l = uint.readUint64(data, pos + 1, options) 84 | if (typeof l === 'bigint') { 85 | throw new Error(`${decodeErrPrefix} 64-bit integer string lengths not supported`) 86 | } 87 | return toToken(data, pos, 9, l, options) 88 | } 89 | 90 | export const encodeString = encodeBytes 91 | -------------------------------------------------------------------------------- /test/test-6tag.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { Token, Type } from '../lib/token.js' 6 | import { decode, encode } from '../cborg.js' 7 | import { fromHex, toHex } from '../lib/byte-utils.js' 8 | import { dateDecoder, dateEncoder } from './common.js' 9 | 10 | const { assert } = chai 11 | 12 | function Uint16ArrayDecoder (obj) { 13 | if (typeof obj !== 'string') { 14 | throw new Error('expected string for tag 23') 15 | } 16 | const u8a = fromHex(obj) 17 | return new Uint16Array(u8a.buffer, u8a.byteOffset, u8a.length / 2) 18 | } 19 | 20 | function Uint16ArrayEncoder (obj) { 21 | if (!(obj instanceof Uint16Array)) { 22 | throw new Error('expected Uint16Array for "Uint16Array" encoder') 23 | } 24 | return [ 25 | new Token(Type.tag, 23), 26 | new Token(Type.string, toHex(obj)) 27 | ] 28 | } 29 | 30 | describe('tag', () => { 31 | it('date', () => { 32 | assert.throws(() => encode({ d: new Date() }), /unsupported type: Date/) 33 | 34 | assert.equal( 35 | toHex(encode(new Date('2013-03-21T20:04:00Z'), { typeEncoders: { Date: dateEncoder } })), 36 | 'c074323031332d30332d32315432303a30343a30305a' // from appendix_a 37 | ) 38 | 39 | const decodedDate = decode(fromHex('c074323031332d30332d32315432303a30343a30305a'), { tags: { 0: dateDecoder } }) 40 | assert.instanceOf(decodedDate, Date) 41 | assert.equal(decodedDate.toISOString(), new Date('2013-03-21T20:04:00Z').toISOString()) 42 | }) 43 | 44 | it('Uint16Array as hex/23 (overide existing type)', () => { 45 | assert.equal( 46 | toHex(encode(Uint16Array.from([1, 2, 3]), { typeEncoders: { Uint16Array: Uint16ArrayEncoder } })), 47 | 'd76c303130303032303030333030' // tag(23) + string('010002000300') 48 | ) 49 | 50 | const decoded = decode(fromHex('d76c303130303032303030333030'), { tags: { 23: Uint16ArrayDecoder } }) 51 | assert.instanceOf(decoded, Uint16Array) 52 | assert.equal(toHex(decoded), toHex(Uint16Array.from([1, 2, 3]))) 53 | }) 54 | 55 | it('tag int too large', () => { 56 | const verify = (hex, strict) => { 57 | if (!strict) { 58 | assert.throws( 59 | () => decode(fromHex(hex), { tags: { 8: dateDecoder }, strict: true }), 60 | /integer encoded in more bytes than necessary/) 61 | } 62 | const decodedDate = decode(fromHex(hex), { tags: { 8: dateDecoder }, strict }) 63 | assert.instanceOf(decodedDate, Date) 64 | assert.equal(decodedDate.toISOString(), new Date('2013-03-21T20:04:00Z').toISOString()) 65 | } 66 | // compact 67 | verify('c874323031332d30332d32315432303a30343a30305a', true) 68 | // int8 69 | verify('d80874323031332d30332d32315432303a30343a30305a', false) 70 | // int16 71 | verify('d9000874323031332d30332d32315432303a30343a30305a', false) 72 | // int32 73 | verify('da0000000874323031332d30332d32315432303a30343a30305a', false) 74 | // int64 75 | verify('db000000000000000874323031332d30332d32315432303a30343a30305a', false) 76 | }) 77 | 78 | /* 79 | describe('taglib', () => { 80 | it('bigint', () => { 81 | const v = BigInt(2) ** BigInt(80) 82 | }) 83 | }) 84 | */ 85 | }) 86 | -------------------------------------------------------------------------------- /test/test-cbor-vectors.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha,es2020 */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode, encode } from '../cborg.js' 6 | import * as taglib from 'cborg/taglib' 7 | import { fromHex, toHex } from '../lib/byte-utils.js' 8 | // fixtures from https://github.com/cbor/test-vectors 9 | import { fixtures } from './appendix_a.js' 10 | 11 | const { assert } = chai 12 | 13 | const tags = [] 14 | const typeEncoders = {} 15 | 16 | tags[0] = function (obj) { 17 | if (typeof obj !== 'string') { 18 | throw new Error('expected string for tag 1') 19 | } 20 | return `0("${new Date(obj).toISOString().replace(/\.000Z$/, 'Z')}")` 21 | } 22 | 23 | tags[1] = function (obj) { 24 | if (typeof obj !== 'number') { 25 | throw new Error('expected number for tag 1') 26 | } 27 | return `1(${obj})` 28 | } 29 | 30 | tags[2] = taglib.bigIntDecoder 31 | typeEncoders.bigint = taglib.bigIntEncoder 32 | tags[3] = taglib.bigNegIntDecoder 33 | 34 | tags[23] = function (obj) { 35 | // expected conversion to base16 36 | if (!(obj instanceof Uint8Array)) { 37 | throw new Error('expected byte array for tag 23') 38 | } 39 | return `23(h'${toHex(obj)}')` 40 | } 41 | 42 | tags[24] = function (obj) { // embedded cbor, oh my 43 | return tags[23](obj).replace(/^23/, '24') 44 | } 45 | 46 | tags[32] = function (obj) { // url 47 | if (typeof obj !== 'string') { 48 | throw new Error('expected string for tag 32') 49 | } 50 | ;(() => new URL(obj))() // will throw if not a url 51 | return `32("${obj}")` 52 | } 53 | 54 | describe('cbor/test-vectors', () => { 55 | let i = 0 56 | for (const fixture of fixtures) { 57 | const u8a = fromHex(fixture.hex) 58 | let expected = fixture.decoded !== undefined ? fixture.decoded : fixture.diagnostic 59 | 60 | if (typeof expected === 'string' && expected.startsWith('h\'')) { 61 | expected = fromHex(expected.replace(/(^h)'|('$)/g, '')) 62 | } 63 | 64 | it(`test vector #${i}: ${inspect(expected).replace(/\n\s*/g, '')}`, () => { 65 | if (fixture.error) { 66 | assert.throws(() => decode(u8a, { tags }), fixture.error) 67 | } else { 68 | if (fixture.noTagDecodeError) { 69 | assert.throws(() => decode(u8a), fixture.noTagDecodeError) 70 | } 71 | let actual = decode(u8a, { tags }) 72 | if (typeof actual === 'bigint') { 73 | actual = inspect(actual) 74 | } 75 | if (typeof expected === 'bigint') { 76 | expected = inspect(expected) 77 | } 78 | assert.deepEqual(actual, expected) 79 | 80 | if (fixture.roundtrip) { 81 | if (fixture.noTagEncodeError) { 82 | assert.throws(() => encode(decode(u8a, { tags })), fixture.noTagEncodeError) 83 | } 84 | const reencoded = encode(decode(u8a, { tags }), { typeEncoders }) 85 | assert.equal(toHex(reencoded), fixture.hex) 86 | } 87 | } 88 | }) 89 | i++ 90 | } 91 | 92 | it.skip('encode w/ tags', () => { 93 | }) 94 | }) 95 | 96 | function inspect (o) { 97 | if (typeof o === 'string') { 98 | return `'${o}'` 99 | } 100 | if (o instanceof Uint8Array) { 101 | return `Uint8Array<${o.join(',')}>` 102 | } 103 | if (o == null || typeof o !== 'object') { 104 | return String(o) 105 | } 106 | return JSON.stringify(o) 107 | } 108 | -------------------------------------------------------------------------------- /types/lib/0uint.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./bl.js').Bl} Bl 3 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 4 | */ 5 | /** 6 | * @param {Uint8Array} data 7 | * @param {number} offset 8 | * @param {DecodeOptions} options 9 | * @returns {number} 10 | */ 11 | export function readUint8(data: Uint8Array, offset: number, options: DecodeOptions): number; 12 | /** 13 | * @param {Uint8Array} data 14 | * @param {number} offset 15 | * @param {DecodeOptions} options 16 | * @returns {number} 17 | */ 18 | export function readUint16(data: Uint8Array, offset: number, options: DecodeOptions): number; 19 | /** 20 | * @param {Uint8Array} data 21 | * @param {number} offset 22 | * @param {DecodeOptions} options 23 | * @returns {number} 24 | */ 25 | export function readUint32(data: Uint8Array, offset: number, options: DecodeOptions): number; 26 | /** 27 | * @param {Uint8Array} data 28 | * @param {number} offset 29 | * @param {DecodeOptions} options 30 | * @returns {number|bigint} 31 | */ 32 | export function readUint64(data: Uint8Array, offset: number, options: DecodeOptions): number | bigint; 33 | /** 34 | * @param {Uint8Array} data 35 | * @param {number} pos 36 | * @param {number} _minor 37 | * @param {DecodeOptions} options 38 | * @returns {Token} 39 | */ 40 | export function decodeUint8(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 41 | /** 42 | * @param {Uint8Array} data 43 | * @param {number} pos 44 | * @param {number} _minor 45 | * @param {DecodeOptions} options 46 | * @returns {Token} 47 | */ 48 | export function decodeUint16(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 49 | /** 50 | * @param {Uint8Array} data 51 | * @param {number} pos 52 | * @param {number} _minor 53 | * @param {DecodeOptions} options 54 | * @returns {Token} 55 | */ 56 | export function decodeUint32(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 57 | /** 58 | * @param {Uint8Array} data 59 | * @param {number} pos 60 | * @param {number} _minor 61 | * @param {DecodeOptions} options 62 | * @returns {Token} 63 | */ 64 | export function decodeUint64(data: Uint8Array, pos: number, _minor: number, options: DecodeOptions): Token; 65 | /** 66 | * @param {Bl} buf 67 | * @param {Token} token 68 | */ 69 | export function encodeUint(buf: Bl, token: Token): void; 70 | export namespace encodeUint { 71 | /** 72 | * @param {Token} token 73 | * @returns {number} 74 | */ 75 | function encodedSize(token: Token): number; 76 | /** 77 | * @param {Token} tok1 78 | * @param {Token} tok2 79 | * @returns {number} 80 | */ 81 | function compareTokens(tok1: Token, tok2: Token): number; 82 | } 83 | /** 84 | * @param {Bl} buf 85 | * @param {number} major 86 | * @param {number|bigint} uint 87 | */ 88 | export function encodeUintValue(buf: Bl, major: number, uint: number | bigint): void; 89 | export namespace encodeUintValue { 90 | /** 91 | * @param {number} uint 92 | * @returns {number} 93 | */ 94 | function encodedSize(uint: number): number; 95 | } 96 | export const uintBoundaries: (number | bigint)[]; 97 | export type Bl = import("./bl.js").Bl; 98 | export type DecodeOptions = import("../interface").DecodeOptions; 99 | import { Token } from './token.js'; 100 | //# sourceMappingURL=0uint.d.ts.map -------------------------------------------------------------------------------- /lib/5map.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './token.js' 2 | import * as uint from './0uint.js' 3 | import { decodeErrPrefix } from './common.js' 4 | 5 | /** 6 | * @typedef {import('./bl.js').Bl} Bl 7 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 8 | */ 9 | 10 | /** 11 | * @param {Uint8Array} _data 12 | * @param {number} _pos 13 | * @param {number} prefix 14 | * @param {number} length 15 | * @returns {Token} 16 | */ 17 | function toToken (_data, _pos, prefix, length) { 18 | return new Token(Type.map, length, prefix) 19 | } 20 | 21 | /** 22 | * @param {Uint8Array} data 23 | * @param {number} pos 24 | * @param {number} minor 25 | * @param {DecodeOptions} _options 26 | * @returns {Token} 27 | */ 28 | export function decodeMapCompact (data, pos, minor, _options) { 29 | return toToken(data, pos, 1, minor) 30 | } 31 | 32 | /** 33 | * @param {Uint8Array} data 34 | * @param {number} pos 35 | * @param {number} _minor 36 | * @param {DecodeOptions} options 37 | * @returns {Token} 38 | */ 39 | export function decodeMap8 (data, pos, _minor, options) { 40 | return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options)) 41 | } 42 | 43 | /** 44 | * @param {Uint8Array} data 45 | * @param {number} pos 46 | * @param {number} _minor 47 | * @param {DecodeOptions} options 48 | * @returns {Token} 49 | */ 50 | export function decodeMap16 (data, pos, _minor, options) { 51 | return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options)) 52 | } 53 | 54 | /** 55 | * @param {Uint8Array} data 56 | * @param {number} pos 57 | * @param {number} _minor 58 | * @param {DecodeOptions} options 59 | * @returns {Token} 60 | */ 61 | export function decodeMap32 (data, pos, _minor, options) { 62 | return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options)) 63 | } 64 | 65 | // TODO: maybe we shouldn't support this .. 66 | /** 67 | * @param {Uint8Array} data 68 | * @param {number} pos 69 | * @param {number} _minor 70 | * @param {DecodeOptions} options 71 | * @returns {Token} 72 | */ 73 | export function decodeMap64 (data, pos, _minor, options) { 74 | const l = uint.readUint64(data, pos + 1, options) 75 | if (typeof l === 'bigint') { 76 | throw new Error(`${decodeErrPrefix} 64-bit integer map lengths not supported`) 77 | } 78 | return toToken(data, pos, 9, l) 79 | } 80 | 81 | /** 82 | * @param {Uint8Array} data 83 | * @param {number} pos 84 | * @param {number} _minor 85 | * @param {DecodeOptions} options 86 | * @returns {Token} 87 | */ 88 | export function decodeMapIndefinite (data, pos, _minor, options) { 89 | if (options.allowIndefinite === false) { 90 | throw new Error(`${decodeErrPrefix} indefinite length items not allowed`) 91 | } 92 | return toToken(data, pos, 1, Infinity) 93 | } 94 | 95 | /** 96 | * @param {Bl} buf 97 | * @param {Token} token 98 | */ 99 | export function encodeMap (buf, token) { 100 | uint.encodeUintValue(buf, Type.map.majorEncoded, token.value) 101 | } 102 | 103 | // using a map as a map key, are you sure about this? we can only sort 104 | // by map length here, it's up to the encoder to decide to look deeper 105 | encodeMap.compareTokens = uint.encodeUint.compareTokens 106 | 107 | /** 108 | * @param {Token} token 109 | * @returns {number} 110 | */ 111 | encodeMap.encodedSize = function encodedSize (token) { 112 | return uint.encodeUintValue.encodedSize(token.value) 113 | } 114 | -------------------------------------------------------------------------------- /lib/4array.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './token.js' 2 | import * as uint from './0uint.js' 3 | import { decodeErrPrefix } from './common.js' 4 | 5 | /** 6 | * @typedef {import('./bl.js').Bl} Bl 7 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 8 | */ 9 | 10 | /** 11 | * @param {Uint8Array} _data 12 | * @param {number} _pos 13 | * @param {number} prefix 14 | * @param {number} length 15 | * @returns {Token} 16 | */ 17 | function toToken (_data, _pos, prefix, length) { 18 | return new Token(Type.array, length, prefix) 19 | } 20 | 21 | /** 22 | * @param {Uint8Array} data 23 | * @param {number} pos 24 | * @param {number} minor 25 | * @param {DecodeOptions} _options 26 | * @returns {Token} 27 | */ 28 | export function decodeArrayCompact (data, pos, minor, _options) { 29 | return toToken(data, pos, 1, minor) 30 | } 31 | 32 | /** 33 | * @param {Uint8Array} data 34 | * @param {number} pos 35 | * @param {number} _minor 36 | * @param {DecodeOptions} options 37 | * @returns {Token} 38 | */ 39 | export function decodeArray8 (data, pos, _minor, options) { 40 | return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options)) 41 | } 42 | 43 | /** 44 | * @param {Uint8Array} data 45 | * @param {number} pos 46 | * @param {number} _minor 47 | * @param {DecodeOptions} options 48 | * @returns {Token} 49 | */ 50 | export function decodeArray16 (data, pos, _minor, options) { 51 | return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options)) 52 | } 53 | 54 | /** 55 | * @param {Uint8Array} data 56 | * @param {number} pos 57 | * @param {number} _minor 58 | * @param {DecodeOptions} options 59 | * @returns {Token} 60 | */ 61 | export function decodeArray32 (data, pos, _minor, options) { 62 | return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options)) 63 | } 64 | 65 | // TODO: maybe we shouldn't support this .. 66 | /** 67 | * @param {Uint8Array} data 68 | * @param {number} pos 69 | * @param {number} _minor 70 | * @param {DecodeOptions} options 71 | * @returns {Token} 72 | */ 73 | export function decodeArray64 (data, pos, _minor, options) { 74 | const l = uint.readUint64(data, pos + 1, options) 75 | if (typeof l === 'bigint') { 76 | throw new Error(`${decodeErrPrefix} 64-bit integer array lengths not supported`) 77 | } 78 | return toToken(data, pos, 9, l) 79 | } 80 | 81 | /** 82 | * @param {Uint8Array} data 83 | * @param {number} pos 84 | * @param {number} _minor 85 | * @param {DecodeOptions} options 86 | * @returns {Token} 87 | */ 88 | export function decodeArrayIndefinite (data, pos, _minor, options) { 89 | if (options.allowIndefinite === false) { 90 | throw new Error(`${decodeErrPrefix} indefinite length items not allowed`) 91 | } 92 | return toToken(data, pos, 1, Infinity) 93 | } 94 | 95 | /** 96 | * @param {Bl} buf 97 | * @param {Token} token 98 | */ 99 | export function encodeArray (buf, token) { 100 | uint.encodeUintValue(buf, Type.array.majorEncoded, token.value) 101 | } 102 | 103 | // using an array as a map key, are you sure about this? we can only sort 104 | // by map length here, it's up to the encoder to decide to look deeper 105 | encodeArray.compareTokens = uint.encodeUint.compareTokens 106 | 107 | /** 108 | * @param {Token} token 109 | * @returns {number} 110 | */ 111 | encodeArray.encodedSize = function encodedSize (token) { 112 | return uint.encodeUintValue.encodedSize(token.value) 113 | } 114 | -------------------------------------------------------------------------------- /lib/1negint.js: -------------------------------------------------------------------------------- 1 | /* eslint-env es2020 */ 2 | 3 | import { Token, Type } from './token.js' 4 | import * as uint from './0uint.js' 5 | import { decodeErrPrefix } from './common.js' 6 | 7 | /** 8 | * @typedef {import('./bl.js').Bl} Bl 9 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 10 | */ 11 | 12 | /** 13 | * @param {Uint8Array} data 14 | * @param {number} pos 15 | * @param {number} _minor 16 | * @param {DecodeOptions} options 17 | * @returns {Token} 18 | */ 19 | export function decodeNegint8 (data, pos, _minor, options) { 20 | return new Token(Type.negint, -1 - uint.readUint8(data, pos + 1, options), 2) 21 | } 22 | 23 | /** 24 | * @param {Uint8Array} data 25 | * @param {number} pos 26 | * @param {number} _minor 27 | * @param {DecodeOptions} options 28 | * @returns {Token} 29 | */ 30 | export function decodeNegint16 (data, pos, _minor, options) { 31 | return new Token(Type.negint, -1 - uint.readUint16(data, pos + 1, options), 3) 32 | } 33 | 34 | /** 35 | * @param {Uint8Array} data 36 | * @param {number} pos 37 | * @param {number} _minor 38 | * @param {DecodeOptions} options 39 | * @returns {Token} 40 | */ 41 | export function decodeNegint32 (data, pos, _minor, options) { 42 | return new Token(Type.negint, -1 - uint.readUint32(data, pos + 1, options), 5) 43 | } 44 | 45 | const neg1b = BigInt(-1) 46 | const pos1b = BigInt(1) 47 | 48 | /** 49 | * @param {Uint8Array} data 50 | * @param {number} pos 51 | * @param {number} _minor 52 | * @param {DecodeOptions} options 53 | * @returns {Token} 54 | */ 55 | export function decodeNegint64 (data, pos, _minor, options) { 56 | const int = uint.readUint64(data, pos + 1, options) 57 | if (typeof int !== 'bigint') { 58 | const value = -1 - int 59 | if (value >= Number.MIN_SAFE_INTEGER) { 60 | return new Token(Type.negint, value, 9) 61 | } 62 | } 63 | if (options.allowBigInt !== true) { 64 | throw new Error(`${decodeErrPrefix} integers outside of the safe integer range are not supported`) 65 | } 66 | return new Token(Type.negint, neg1b - BigInt(int), 9) 67 | } 68 | 69 | /** 70 | * @param {Bl} buf 71 | * @param {Token} token 72 | */ 73 | export function encodeNegint (buf, token) { 74 | const negint = token.value 75 | const unsigned = (typeof negint === 'bigint' ? (negint * neg1b - pos1b) : (negint * -1 - 1)) 76 | uint.encodeUintValue(buf, token.type.majorEncoded, unsigned) 77 | } 78 | 79 | /** 80 | * @param {Token} token 81 | * @returns {number} 82 | */ 83 | encodeNegint.encodedSize = function encodedSize (token) { 84 | const negint = token.value 85 | const unsigned = (typeof negint === 'bigint' ? (negint * neg1b - pos1b) : (negint * -1 - 1)) 86 | /* c8 ignore next 4 */ 87 | // handled by quickEncode, we shouldn't get here but it's included for completeness 88 | if (unsigned < uint.uintBoundaries[0]) { 89 | return 1 90 | } 91 | if (unsigned < uint.uintBoundaries[1]) { 92 | return 2 93 | } 94 | if (unsigned < uint.uintBoundaries[2]) { 95 | return 3 96 | } 97 | if (unsigned < uint.uintBoundaries[3]) { 98 | return 5 99 | } 100 | return 9 101 | } 102 | 103 | /** 104 | * @param {Token} tok1 105 | * @param {Token} tok2 106 | * @returns {number} 107 | */ 108 | encodeNegint.compareTokens = function compareTokens (tok1, tok2) { 109 | // opposite of the uint comparison since we store the uint version in bytes 110 | return tok1.value < tok2.value ? 1 : tok1.value > tok2.value ? -1 : /* c8 ignore next */ 0 111 | } 112 | -------------------------------------------------------------------------------- /lib/diagnostic_test.js: -------------------------------------------------------------------------------- 1 | import { tokensToDiagnostic } from './diagnostic.js' 2 | import { fromHex } from './byte-utils.js' 3 | 4 | const inp = ` 5 | a7 6 | 62 7 | 6964 8 | 78 40 9 | 6469643a6578616d706c653a31324433 10 | 4b6f6f574d4864727a6377706a626472 11 | 5a733547477145524176636771583362 12 | 3564707550745061396f743639796577 13 | 65 14 | 70726f6f66 15 | a4 16 | 64 17 | 74797065 18 | 74 19 | 656432353531395369676e617475726532303138 20 | 67 21 | 63726561746564 22 | 74 23 | 323032302d30352d30315430333a30303a30325a 24 | 67 25 | 63726561746f72 26 | 78 8c 27 | 6469643a6578616d706c653a31324433 28 | 4b6f6f574d4864727a6377706a626472 29 | 5a733547477145524176636771583362 30 | 3564707550745061396f743639796577 31 | 3b206578616d706c653a6b65793d6964 32 | 3d626166797265696375627478357771 33 | 6f336e6f73633463617a726b63746668 34 | 776436726577657a6770776f65347377 35 | 69726c733465626468733269 36 | 6e 37 | 7369676e617475726556616c7565 38 | 78 58 39 | 6f3972364c78676f474e38466f616565 40 | 554136456444637631324776447a4645 41 | 6d43676a577a76707572325953517941 42 | 3857327230535357554b2b6e4835744d 43 | 717a61464c756e3677775a31456f7433 44 | 37616d4744673d3d 45 | 67 46 | 63726561746564 47 | 74 48 | 323031382d31322d30315430333a30303a30305a 49 | 67 50 | 75706461746564 51 | 74 52 | 323032302d30352d30315430333a30303a30305a 53 | 68 54 | 40636f6e74657874 55 | 78 1c 56 | 68747470733a2f2f7777772e77332e6f 57 | 72672f6e732f6469642f7631 58 | 69 59 | 7075626c69634b6579 60 | 81 61 | a5 62 | 62 63 | 6964 64 | 78 85 65 | 6261667972656963756274783577716f 66 | 336e6f73633463617a726b6374666877 67 | 6436726577657a6770776f6534737769 68 | 726c7334656264687332693b6578616d 69 | 706c653a6b65793d6964626166797265 70 | 6963756274783577716f336e6f736334 71 | 63617a726b6374666877643672657765 72 | 7a6770776f6534737769726c73346562 73 | 6468733269 74 | 64 75 | 74797065 76 | 6e 77 | 45644473615075626c69634b6579 78 | 65 79 | 6375727665 80 | 67 81 | 65643235353139 82 | 67 83 | 65787069726573 84 | 74 85 | 323031392d31322d30315430333a30303a30305a 86 | 6f 87 | 7075626c69634b6579426173653634 88 | 78 2c 89 | 716d7a3774704c4e4b4b4b646c376344 90 | 375062656a4469425670374f4e706d5a 91 | 62666d633763454b396d673d 92 | 6e 93 | 61757468656e7469636174696f6e 94 | 81 95 | 78 83 96 | 6469643a6578616d706c653a31324433 97 | 4b6f6f574d4864727a6377706a626472 98 | 5a733547477145524176636771583362 99 | 3564707550745061396f743639796577 100 | 3b6b65792d69643d6261667972656963 101 | 756274783577716f336e6f7363346361 102 | 7a726b63746668776436726577657a67 103 | 70776f6534737769726c733465626468 104 | 733269 105 | `.replace(/[\n ]/g, '') 106 | 107 | // tokensToDiagnostic(fromHex('861864fb4376345785d8a0002063796570428411fb3ff199999999999a')) 108 | for (const line of tokensToDiagnostic(fromHex('851864fb4376345785d8a00020a661624284116166f46168f7616ef6617379012c7965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965707965706174f5fb3ff199999999999a'))) { 109 | console.log(line) 110 | } 111 | for (const line of tokensToDiagnostic(fromHex(inp))) { 112 | console.log(line) 113 | } 114 | 115 | for (const line of tokensToDiagnostic(fromHex('82410181a161318182410041ff'))) { 116 | console.log(line) 117 | } 118 | -------------------------------------------------------------------------------- /test/test-partial.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | import { garbage } from 'ipld-garbage' 5 | import { uintBoundaries } from '../lib/0uint.js' 6 | import { encode, decodeFirst } from '../cborg.js' 7 | import { dateDecoder, dateEncoder } from './common.js' 8 | 9 | const { assert } = chai 10 | 11 | function verifyPartial (objects, options) { 12 | const encoded = [] 13 | const lengths = [] 14 | let length = 0 15 | for (const object of Array.isArray(objects) ? objects : [objects]) { 16 | encoded.push(encode(object, options)) 17 | const l = encoded[encoded.length - 1].length 18 | length += l 19 | lengths.push(l) 20 | } 21 | const buf = new Uint8Array(length) 22 | let offset = 0 23 | for (const enc of encoded) { 24 | buf.set(enc, offset) 25 | offset += enc.length 26 | } 27 | let partial = buf 28 | for (let ii = 0; ii < encoded.length; ii++) { 29 | const [decoded, remainder] = decodeFirst(partial, options) 30 | assert.deepEqual(decoded, objects[ii]) 31 | assert.equal(remainder.length, partial.length - lengths[ii]) 32 | partial = remainder 33 | } 34 | assert.equal(partial.length, 0) // just to be sure 35 | } 36 | 37 | describe('decodePartial', () => { 38 | describe('multiple', () => { 39 | it('simple', () => { 40 | verifyPartial([1, 2, 3]) 41 | verifyPartial([8.940696716308594e-08, 1]) 42 | verifyPartial([ 43 | [], 44 | [1, 2, { obj: 1.5 }, null, new Uint8Array([1, 2, 3])], 45 | { boop: true, bop: 1 }, 46 | 'nope', 47 | { o: 'nope' }, 48 | new Uint8Array([1, 2, 3]), 49 | true, 50 | null 51 | ]) 52 | }) 53 | 54 | it('options', () => { 55 | const m = new Map() 56 | m.set('a', 1) 57 | m.set('b', null) 58 | m.set('c', 'grok') 59 | m.set('date', new Date('2013-03-21T20:04:00Z')) 60 | verifyPartial( 61 | [8.940696716308594e-08, 1, null, 'grok', new Date('2013-03-21T20:04:00Z'), 62 | [8.940696716308594e-08, 1, null, 'grok', new Date('2013-03-21T20:04:00Z')], 63 | m 64 | ], 65 | { typeEncoders: { Date: dateEncoder }, useMaps: true, tags: { 0: dateDecoder } }) 66 | }) 67 | 68 | it('garbage', function () { 69 | this.timeout(10000) 70 | for (let ii = 0; ii < 10; ii++) { 71 | const gbg = [] 72 | for (let ii = 0; ii < 100; ii++) { 73 | gbg.push(garbage(1 << 6, { weights: { CID: 0 } })) 74 | } 75 | verifyPartial(gbg) 76 | } 77 | }) 78 | }) 79 | 80 | it('singular', () => { 81 | it('int boundaries', () => { 82 | for (let ii = 0; ii < 4; ii++) { 83 | verifyPartial(uintBoundaries[ii]) 84 | verifyPartial(uintBoundaries[ii] - 1) 85 | verifyPartial(uintBoundaries[ii] + 1) 86 | verifyPartial(-1 * uintBoundaries[ii]) 87 | verifyPartial(-1 * uintBoundaries[ii] - 1) 88 | verifyPartial(-1 * uintBoundaries[ii] + 1) 89 | } 90 | }) 91 | 92 | it('tags', () => { 93 | verifyPartial({ date: new Date('2013-03-21T20:04:00Z') }, { typeEncoders: { Date: dateEncoder } }) 94 | }) 95 | 96 | it('floats', () => { 97 | verifyPartial(0.5) 98 | verifyPartial(0.5, { float64: true }) 99 | verifyPartial(8.940696716308594e-08) 100 | verifyPartial(8.940696716308594e-08, { float64: true }) 101 | }) 102 | 103 | it('small garbage', function () { 104 | this.timeout(10000) 105 | for (let ii = 0; ii < 1000; ii++) { 106 | const gbg = garbage(1 << 6, { weights: { CID: 0 } }) 107 | verifyPartial(gbg) 108 | } 109 | }) 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /lib/2bytes.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './token.js' 2 | import { assertEnoughData, decodeErrPrefix } from './common.js' 3 | import * as uint from './0uint.js' 4 | import { compare, fromString, slice } from './byte-utils.js' 5 | 6 | /** 7 | * @typedef {import('./bl.js').Bl} Bl 8 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 9 | */ 10 | 11 | /** 12 | * @param {Uint8Array} data 13 | * @param {number} pos 14 | * @param {number} prefix 15 | * @param {number} length 16 | * @returns {Token} 17 | */ 18 | function toToken (data, pos, prefix, length) { 19 | assertEnoughData(data, pos, prefix + length) 20 | const buf = slice(data, pos + prefix, pos + prefix + length) 21 | return new Token(Type.bytes, buf, prefix + length) 22 | } 23 | 24 | /** 25 | * @param {Uint8Array} data 26 | * @param {number} pos 27 | * @param {number} minor 28 | * @param {DecodeOptions} _options 29 | * @returns {Token} 30 | */ 31 | export function decodeBytesCompact (data, pos, minor, _options) { 32 | return toToken(data, pos, 1, minor) 33 | } 34 | 35 | /** 36 | * @param {Uint8Array} data 37 | * @param {number} pos 38 | * @param {number} _minor 39 | * @param {DecodeOptions} options 40 | * @returns {Token} 41 | */ 42 | export function decodeBytes8 (data, pos, _minor, options) { 43 | return toToken(data, pos, 2, uint.readUint8(data, pos + 1, options)) 44 | } 45 | 46 | /** 47 | * @param {Uint8Array} data 48 | * @param {number} pos 49 | * @param {number} _minor 50 | * @param {DecodeOptions} options 51 | * @returns {Token} 52 | */ 53 | export function decodeBytes16 (data, pos, _minor, options) { 54 | return toToken(data, pos, 3, uint.readUint16(data, pos + 1, options)) 55 | } 56 | 57 | /** 58 | * @param {Uint8Array} data 59 | * @param {number} pos 60 | * @param {number} _minor 61 | * @param {DecodeOptions} options 62 | * @returns {Token} 63 | */ 64 | export function decodeBytes32 (data, pos, _minor, options) { 65 | return toToken(data, pos, 5, uint.readUint32(data, pos + 1, options)) 66 | } 67 | 68 | // TODO: maybe we shouldn't support this .. 69 | /** 70 | * @param {Uint8Array} data 71 | * @param {number} pos 72 | * @param {number} _minor 73 | * @param {DecodeOptions} options 74 | * @returns {Token} 75 | */ 76 | export function decodeBytes64 (data, pos, _minor, options) { 77 | const l = uint.readUint64(data, pos + 1, options) 78 | if (typeof l === 'bigint') { 79 | throw new Error(`${decodeErrPrefix} 64-bit integer bytes lengths not supported`) 80 | } 81 | return toToken(data, pos, 9, l) 82 | } 83 | 84 | /** 85 | * `encodedBytes` allows for caching when we do a byte version of a string 86 | * for key sorting purposes 87 | * @param {Token} token 88 | * @returns {Uint8Array} 89 | */ 90 | function tokenBytes (token) { 91 | if (token.encodedBytes === undefined) { 92 | token.encodedBytes = token.type === Type.string ? fromString(token.value) : token.value 93 | } 94 | // @ts-ignore c'mon 95 | return token.encodedBytes 96 | } 97 | 98 | /** 99 | * @param {Bl} buf 100 | * @param {Token} token 101 | */ 102 | export function encodeBytes (buf, token) { 103 | const bytes = tokenBytes(token) 104 | uint.encodeUintValue(buf, token.type.majorEncoded, bytes.length) 105 | buf.push(bytes) 106 | } 107 | 108 | /** 109 | * @param {Token} token 110 | * @returns {number} 111 | */ 112 | encodeBytes.encodedSize = function encodedSize (token) { 113 | const bytes = tokenBytes(token) 114 | return uint.encodeUintValue.encodedSize(bytes.length) + bytes.length 115 | } 116 | 117 | /** 118 | * @param {Token} tok1 119 | * @param {Token} tok2 120 | * @returns {number} 121 | */ 122 | encodeBytes.compareTokens = function compareTokens (tok1, tok2) { 123 | return compareBytes(tokenBytes(tok1), tokenBytes(tok2)) 124 | } 125 | 126 | /** 127 | * @param {Uint8Array} b1 128 | * @param {Uint8Array} b2 129 | * @returns {number} 130 | */ 131 | export function compareBytes (b1, b2) { 132 | return b1.length < b2.length ? -1 : b1.length > b2.length ? 1 : compare(b1, b2) 133 | } 134 | -------------------------------------------------------------------------------- /test/test-1negint.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode, encode } from '../cborg.js' 6 | import { fromHex, toHex } from '../lib/byte-utils.js' 7 | 8 | const { assert } = chai 9 | 10 | // some from https://github.com/PJK/libcbor 11 | 12 | const fixtures = [ 13 | { data: '20', expected: -1, type: 'negint8' }, 14 | { data: '22', expected: -3, type: 'negint8' }, 15 | { data: '3863', expected: -100, type: 'negint8' }, 16 | { data: '38ff', expected: -256, type: 'negint8' }, 17 | { data: '3900ff', expected: -256, type: 'negint16', strict: false }, 18 | { data: '3901f4', expected: -501, type: 'negint16' }, 19 | { data: '3a000000ff', expected: -256, type: 'negint32', strict: false }, 20 | { data: '3aa5f702b3', expected: -2784428724, type: 'negint32' }, 21 | { data: '3b00000000000000ff', expected: -256, type: 'negint32', strict: false }, 22 | { data: '3b0016db6db6db6db7', expected: Number.MIN_SAFE_INTEGER / 1.4 - 1, type: 'negint64' }, 23 | { data: '3b001ffffffffffffe', expected: Number.MIN_SAFE_INTEGER, type: 'negint64' }, 24 | // kind of hard to assert on these (TODO: improve bignum handling) 25 | { data: '3b001fffffffffffff', expected: BigInt('-9007199254740992') /* Number.MIN_SAFE_INTEGER - 1 */, type: 'negint64' }, 26 | { data: '3b0020000000000000', expected: BigInt('-9007199254740993') /* Number.MIN_SAFE_INTEGER - 2 */, type: 'negint64' }, 27 | { data: '3ba5f702b3a5f702b3', expected: BigInt('-11959030306112471732'), type: 'negint64' }, 28 | { data: '3bffffffffffffffff', expected: BigInt('-18446744073709551616'), type: 'negint64' } 29 | ] 30 | 31 | describe('negint', () => { 32 | describe('decode', () => { 33 | for (const fixture of fixtures) { 34 | const data = fromHex(fixture.data) 35 | it(`should decode ${fixture.type}=${fixture.expected}`, () => { 36 | assert.ok(decode(data) === fixture.expected, `decode ${fixture.type} (${decode(data)} != ${fixture.expected})`) 37 | if (fixture.strict === false) { 38 | assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)') 39 | } else { 40 | assert.strictEqual(decode(data, { strict: true }), fixture.expected, `decode ${fixture.type}`) 41 | } 42 | }) 43 | } 44 | }) 45 | 46 | describe('encode', () => { 47 | for (const fixture of fixtures) { 48 | it(`should encode ${fixture.type}=${fixture.expected}`, () => { 49 | if (fixture.strict === false) { 50 | assert.notStrictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type} !strict`) 51 | } else { 52 | assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type}`) 53 | } 54 | }) 55 | } 56 | }) 57 | 58 | // mostly unnecessary, but feels good 59 | describe('roundtrip', () => { 60 | for (const fixture of fixtures) { 61 | it(`should roundtrip ${fixture.type}=${fixture.expected}`, () => { 62 | assert.ok(decode(encode(fixture.expected)) === fixture.expected, `roundtrip ${fixture.type}`) 63 | }) 64 | } 65 | }) 66 | 67 | describe('toobig', () => { 68 | it('bigger than 64-bit', () => { 69 | // boundary condition is right below 64-bit negint 70 | assert.doesNotThrow(() => encode(BigInt('-18446744073709551616') /* BigInt(-1) * BigInt(2) ** BigInt(64) */)) 71 | assert.throws(() => encode(BigInt('-18446744073709551617') /* BigInt(-1) * BigInt(2) ** BigInt(64) - BigInt(1) */), /BigInt larger than allowable range/) 72 | }) 73 | 74 | it('disallow BigInt', () => { 75 | for (const fixture of fixtures) { 76 | const data = fromHex(fixture.data) 77 | if (!Number.isSafeInteger(fixture.expected)) { 78 | assert.throws(() => decode(data, { allowBigInt: false }), /safe integer range/) 79 | } else { 80 | assert.ok(decode(data, { allowBigInt: false }) === fixture.expected, `decode ${fixture.type}`) 81 | } 82 | } 83 | }) 84 | }) 85 | 86 | describe('toosmall', () => { 87 | for (const fixture of fixtures) { 88 | if (fixture.strict !== false && typeof fixture.expected === 'number') { 89 | const small = BigInt(fixture.expected) 90 | it(`should encode ${small}n`, () => { 91 | assert.strictEqual(toHex(encode(BigInt(small))), fixture.data, `encode ${small}`) 92 | }) 93 | } 94 | } 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /test/test-4array.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode, encode } from '../cborg.js' 6 | import { fromHex, toHex } from '../lib/byte-utils.js' 7 | 8 | const { assert } = chai 9 | 10 | const fixtures = [ 11 | { data: '80', expected: [], type: 'array empty' }, 12 | { data: '8102', expected: [2], type: 'array 1 compact uint' }, 13 | { data: '8118ff', expected: [255], type: 'array 1 uint8' }, 14 | { data: '811901f4', expected: [500], type: 'array 1 uint16' }, 15 | { data: '811a00010000', expected: [65536], type: 'array 1 uint32' }, 16 | { data: '811b00000000000000ff', expected: [255], type: 'array 1 uint64', strict: false }, 17 | { data: '811b0016db6db6db6db7', expected: [Number.MAX_SAFE_INTEGER / 1.4], type: 'array 1 uint64' }, 18 | { data: '811b001fffffffffffff', expected: [Number.MAX_SAFE_INTEGER], type: 'array 1 uint64' }, 19 | { data: '8403040506', expected: [3, 4, 5, 6], type: 'array 4 ints' }, 20 | { 21 | data: '8c1b0016db6db6db6db71a000100001901f40200202238ff3aa5f702b33b0016db6db6db6db74261316fc48c6175657320c39f76c49b746521', 22 | expected: [Number.MAX_SAFE_INTEGER / 1.4, 65536, 500, 2, 0, -1, -3, -256, -2784428724, Number.MIN_SAFE_INTEGER / 1.4 - 1, new TextEncoder().encode('a1'), 'Čaues ßvěte!'], 23 | type: 'array mixed terminals', 24 | label: '[]' 25 | }, 26 | { 27 | data: '8265617272617982626f66820582666e657374656482666172726179736121', 28 | expected: ['array', ['of', [5, ['nested', ['arrays', '!']]]]], 29 | type: 'array nested' 30 | }, 31 | // testing lengths encoded as too-large ints 32 | { data: '980403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length8', strict: false }, 33 | { data: '99000403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length16', strict: false }, 34 | { data: '9a0000000403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length32', strict: false }, 35 | { data: '9b000000000000000403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length64', strict: false } 36 | ] 37 | 38 | describe('array', () => { 39 | describe('decode', () => { 40 | for (const fixture of fixtures) { 41 | const data = fromHex(fixture.data) 42 | it(`should decode ${fixture.type}=${fixture.label || fixture.expected}`, () => { 43 | assert.deepStrictEqual(decode(data), fixture.expected, `decode ${fixture.type}`) 44 | if (fixture.strict === false) { 45 | assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)') 46 | } else { 47 | assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${fixture.type}`) 48 | } 49 | }) 50 | 51 | it('should fail to decode very large length', () => { 52 | assert.throws( 53 | () => decode(fromHex('9ba5f702b3a5f7020403040506')), 54 | /CBOR decode error: 64-bit integer array lengths not supported/) 55 | }) 56 | } 57 | }) 58 | 59 | describe('encode', () => { 60 | for (const fixture of fixtures) { 61 | it(`should encode ${fixture.type}=${fixture.label || fixture.expected}`, () => { 62 | if (fixture.unsafe) { 63 | assert.throws(encode.bind(null, fixture.expected), Error, /^CBOR encode error: number too large to encode \(\d+\)$/) 64 | } else if (fixture.strict === false) { 65 | assert.notDeepEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type} !strict`) 66 | } else { 67 | assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type}`) 68 | } 69 | }) 70 | } 71 | }) 72 | 73 | // mostly unnecessary, but feels good 74 | describe('roundtrip', () => { 75 | for (const fixture of fixtures) { 76 | if (!fixture.unsafe && fixture.strict !== false) { 77 | it(`should roundtrip ${fixture.type}=${fixture.label || fixture.expected}`, () => { 78 | assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${fixture.type}`) 79 | }) 80 | } 81 | } 82 | }) 83 | 84 | describe('specials', () => { 85 | it('can decode indefinite length items', () => { 86 | assert.deepStrictEqual(decode(fromHex('9f616f6174ff')), ['o', 't']) 87 | }) 88 | 89 | it('can switch off indefinite length support', () => { 90 | assert.throws(() => decode(fromHex('9f616f6174ff'), { allowIndefinite: false }), /indefinite/) 91 | }) 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /test/test-0uint.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode, encode } from '../cborg.js' 6 | import { fromHex, toHex } from '../lib/byte-utils.js' 7 | 8 | const { assert } = chai 9 | 10 | // some from https://github.com/PJK/libcbor 11 | 12 | const fixtures = [ 13 | { data: '00', expected: 0, type: 'uint8' }, 14 | { data: '02', expected: 2, type: 'uint8' }, 15 | { data: '18ff', expected: 255, type: 'uint8' }, 16 | { data: '1901f4', expected: 500, type: 'uint16' }, 17 | { data: '1900ff', expected: 255, type: 'uint16', strict: false }, 18 | { data: '19ffff', expected: 65535, type: 'uint16' }, 19 | { data: '1a000000ff', expected: 255, type: 'uint32', strict: false }, 20 | { data: '1a00010000', expected: 65536, type: 'uint32' }, 21 | { data: '1a000f4240', expected: 1000000, type: 'uint32' }, 22 | { data: '1aa5f702b3', expected: 2784428723, type: 'uint32' }, 23 | { data: '1b00000000000000ff', expected: 255, type: 'uint64', strict: false }, 24 | { data: '1b0016db6db6db6db7', expected: Number.MAX_SAFE_INTEGER / 1.4, type: 'uint64' }, 25 | { data: '1b001fffffffffffff', expected: Number.MAX_SAFE_INTEGER, type: 'uint64' }, 26 | { data: '1ba5f702b3a5f702b3', expected: BigInt('11959030306112471731'), type: 'uint64' }, 27 | { data: '1bffffffffffffffff', expected: BigInt('18446744073709551615'), type: 'uint64' } 28 | ] 29 | 30 | describe('uint', () => { 31 | describe('decode', () => { 32 | for (const fixture of fixtures) { 33 | const data = fromHex(fixture.data) 34 | it(`should decode ${fixture.type}=${fixture.expected}`, () => { 35 | assert.ok(decode(data) === fixture.expected, `decode ${fixture.type} ${decode(data)} != ${fixture.expected}`) 36 | if (fixture.strict === false) { 37 | assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)') 38 | } else { 39 | assert.ok(decode(data, { strict: true }) === fixture.expected, `decode ${fixture.type}`) 40 | } 41 | }) 42 | } 43 | }) 44 | 45 | it('should throw error', () => { 46 | // minor number 28, too high for uint 47 | assert.throws(() => decode(fromHex('1ca5f702b3a5f702b3')), Error, 'CBOR decode error: encountered invalid minor (28) for major 0') 48 | assert.throws(() => decode(fromHex('1ba5f702b3a5f702')), Error, 'CBOR decode error: not enough data for type') 49 | }) 50 | 51 | describe('encode', () => { 52 | for (const fixture of fixtures) { 53 | it(`should encode ${fixture.type}=${fixture.expected}`, () => { 54 | if (fixture.strict === false) { 55 | assert.notStrictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type} !strict`) 56 | } else { 57 | assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type}`) 58 | } 59 | }) 60 | } 61 | }) 62 | 63 | // mostly unnecessary, but feels good 64 | describe('roundtrip', () => { 65 | for (const fixture of fixtures) { 66 | if (fixture.strict !== false) { 67 | it(`should roundtrip ${fixture.type}=${fixture.expected}`, () => { 68 | assert.ok(decode(encode(fixture.expected)) === fixture.expected, `roundtrip ${fixture.type}`) 69 | }) 70 | } 71 | } 72 | }) 73 | 74 | describe('toobig', () => { 75 | it('bigger than 64-bit', () => { 76 | // boundary condition is right on 64-bit int 77 | assert.doesNotThrow(() => encode(BigInt('18446744073709551615') /* BigInt(2) ** BigInt(64) - BigInt(1) */)) 78 | assert.throws(() => encode(BigInt('18446744073709551616') /* BigInt(2) ** BigInt(64) */), /BigInt larger than allowable range/) 79 | }) 80 | 81 | it('disallow BigInt', () => { 82 | for (const fixture of fixtures) { 83 | const data = fromHex(fixture.data) 84 | if (!Number.isSafeInteger(fixture.expected)) { 85 | assert.throws(() => decode(data, { allowBigInt: false }), /safe integer range/) 86 | } else { 87 | assert.ok(decode(data, { allowBigInt: false }) === fixture.expected, `decode ${fixture.type}`) 88 | } 89 | } 90 | }) 91 | }) 92 | 93 | describe('toosmall', () => { 94 | for (const fixture of fixtures) { 95 | if (fixture.strict !== false && typeof fixture.expected === 'number') { 96 | const small = BigInt(fixture.expected) 97 | it(`should encode ${small}n`, () => { 98 | assert.strictEqual(toHex(encode(BigInt(small))), fixture.data, `encode ${small}`) 99 | }) 100 | } 101 | } 102 | }) 103 | }) 104 | -------------------------------------------------------------------------------- /lib/bl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bl is a list of byte chunks, similar to https://github.com/rvagg/bl but for 3 | * writing rather than reading. 4 | * A Bl object accepts set() operations for individual bytes and copyTo() for 5 | * inserting byte arrays. These write operations don't automatically increment 6 | * the internal cursor so its "length" won't be changed. Instead, increment() 7 | * must be called to extend its length to cover the inserted data. 8 | * The toBytes() call will convert all internal memory to a single Uint8Array of 9 | * the correct length, truncating any data that is stored but hasn't been 10 | * included by an increment(). 11 | * get() can retrieve a single byte. 12 | * All operations (except toBytes()) take an "offset" argument that will perform 13 | * the write at the offset _from the current cursor_. For most operations this 14 | * will be `0` to write at the current cursor position but it can be ahead of 15 | * the current cursor. Negative offsets probably work but are untested. 16 | */ 17 | 18 | // TODO: ipjs doesn't support this, only for test files: https://github.com/mikeal/ipjs/blob/master/src/package/testFile.js#L39 19 | import { alloc, concat, slice } from './byte-utils.js' 20 | 21 | // the ts-ignores in this file are almost all for the `Uint8Array|number[]` duality that exists 22 | // for perf reasons. Consider better approaches to this or removing it entirely, it is quite 23 | // risky because of some assumptions about small chunks === number[] and everything else === Uint8Array. 24 | 25 | const defaultChunkSize = 256 26 | 27 | export class Bl { 28 | /** 29 | * @param {number} [chunkSize] 30 | */ 31 | constructor (chunkSize = defaultChunkSize) { 32 | this.chunkSize = chunkSize 33 | /** @type {number} */ 34 | this.cursor = 0 35 | /** @type {number} */ 36 | this.maxCursor = -1 37 | /** @type {(Uint8Array|number[])[]} */ 38 | this.chunks = [] 39 | // keep the first chunk around if we can to save allocations for future encodes 40 | /** @type {Uint8Array|number[]|null} */ 41 | this._initReuseChunk = null 42 | } 43 | 44 | reset () { 45 | this.cursor = 0 46 | this.maxCursor = -1 47 | if (this.chunks.length) { 48 | this.chunks = [] 49 | } 50 | if (this._initReuseChunk !== null) { 51 | this.chunks.push(this._initReuseChunk) 52 | this.maxCursor = this._initReuseChunk.length - 1 53 | } 54 | } 55 | 56 | /** 57 | * @param {Uint8Array|number[]} bytes 58 | */ 59 | push (bytes) { 60 | let topChunk = this.chunks[this.chunks.length - 1] 61 | const newMax = this.cursor + bytes.length 62 | if (newMax <= this.maxCursor + 1) { 63 | // we have at least one chunk and we can fit these bytes into that chunk 64 | const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1 65 | // @ts-ignore 66 | topChunk.set(bytes, chunkPos) 67 | } else { 68 | // can't fit it in 69 | if (topChunk) { 70 | // trip the last chunk to `cursor` if we need to 71 | const chunkPos = topChunk.length - (this.maxCursor - this.cursor) - 1 72 | if (chunkPos < topChunk.length) { 73 | // @ts-ignore 74 | this.chunks[this.chunks.length - 1] = topChunk.subarray(0, chunkPos) 75 | this.maxCursor = this.cursor - 1 76 | } 77 | } 78 | if (bytes.length < 64 && bytes.length < this.chunkSize) { 79 | // make a new chunk and copy the new one into it 80 | topChunk = alloc(this.chunkSize) 81 | this.chunks.push(topChunk) 82 | this.maxCursor += topChunk.length 83 | if (this._initReuseChunk === null) { 84 | this._initReuseChunk = topChunk 85 | } 86 | // @ts-ignore 87 | topChunk.set(bytes, 0) 88 | } else { 89 | // push the new bytes in as its own chunk 90 | this.chunks.push(bytes) 91 | this.maxCursor += bytes.length 92 | } 93 | } 94 | this.cursor += bytes.length 95 | } 96 | 97 | /** 98 | * @param {boolean} [reset] 99 | * @returns {Uint8Array} 100 | */ 101 | toBytes (reset = false) { 102 | let byts 103 | if (this.chunks.length === 1) { 104 | const chunk = this.chunks[0] 105 | if (reset && this.cursor > chunk.length / 2) { 106 | /* c8 ignore next 2 */ 107 | // @ts-ignore 108 | byts = this.cursor === chunk.length ? chunk : chunk.subarray(0, this.cursor) 109 | this._initReuseChunk = null 110 | this.chunks = [] 111 | } else { 112 | // @ts-ignore 113 | byts = slice(chunk, 0, this.cursor) 114 | } 115 | } else { 116 | // @ts-ignore 117 | byts = concat(this.chunks, this.cursor) 118 | } 119 | if (reset) { 120 | this.reset() 121 | } 122 | return byts 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cborg", 3 | "version": "4.3.2", 4 | "description": "Fast CBOR with a focus on strictness", 5 | "main": "cborg.js", 6 | "type": "module", 7 | "bin": { 8 | "cborg": "lib/bin.js" 9 | }, 10 | "scripts": { 11 | "lint": "standard *.js lib/*.js test/*.js", 12 | "build": "npm run build:types", 13 | "build:types": "tsc --build", 14 | "prepublishOnly": "npm run build", 15 | "test:node": "c8 --check-coverage --exclude=test/** mocha test/test-*.js", 16 | "test:browser": "polendina --cleanup test/test-*.js", 17 | "test": "npm run lint && npm run build && npm run test:node && npm run test:browser", 18 | "test:ci": "npm run test", 19 | "coverage": "c8 --reporter=html --reporter=text mocha test/test-*.js && npx st -d coverage -p 8888" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:rvagg/cborg.git" 24 | }, 25 | "keywords": [ 26 | "cbor" 27 | ], 28 | "author": "Rod (http://r.va.gg/)", 29 | "license": "Apache-2.0", 30 | "devDependencies": { 31 | "@semantic-release/changelog": "^6.0.3", 32 | "@semantic-release/commit-analyzer": "^13.0.0", 33 | "@semantic-release/git": "^10.0.1", 34 | "@semantic-release/github": "^12.0.0", 35 | "@semantic-release/npm": "^13.0.0", 36 | "@semantic-release/release-notes-generator": "^14.0.1", 37 | "@types/chai": "^5.0.0", 38 | "@types/mocha": "^10.0.8", 39 | "@types/node": "^25.0.0", 40 | "c8": "^10.1.2", 41 | "chai": "^6.0.1", 42 | "conventional-changelog-conventionalcommits": "^9.0.0", 43 | "ipld-garbage": "^5.0.0", 44 | "mocha": "^11.0.1", 45 | "polendina": "^3.2.2", 46 | "semantic-release": "^25.0.0", 47 | "standard": "^17.1.2", 48 | "typescript": "^5.6.2" 49 | }, 50 | "exports": { 51 | ".": { 52 | "import": "./cborg.js", 53 | "types": "./types/cborg.d.ts" 54 | }, 55 | "./length": { 56 | "import": "./lib/length.js", 57 | "types": "./types/lib/length.d.ts" 58 | }, 59 | "./taglib": { 60 | "import": "./taglib.js", 61 | "types": "./types/taglib.d.ts" 62 | }, 63 | "./json": { 64 | "import": "./lib/json/json.js", 65 | "types": "./types/lib/json/json.d.ts" 66 | }, 67 | "./interface": { 68 | "types": "./types/interface.d.ts" 69 | } 70 | }, 71 | "types": "cborg.d.ts", 72 | "typesVersions": { 73 | "*": { 74 | "json": [ 75 | "types/lib/json/json.d.ts" 76 | ], 77 | "length": [ 78 | "types/lib/length.d.ts" 79 | ], 80 | "*": [ 81 | "types/*" 82 | ], 83 | "types/*": [ 84 | "types/*" 85 | ] 86 | } 87 | }, 88 | "release": { 89 | "branches": [ 90 | "master" 91 | ], 92 | "plugins": [ 93 | [ 94 | "@semantic-release/commit-analyzer", 95 | { 96 | "preset": "conventionalcommits", 97 | "releaseRules": [ 98 | { 99 | "breaking": true, 100 | "release": "major" 101 | }, 102 | { 103 | "revert": true, 104 | "release": "patch" 105 | }, 106 | { 107 | "type": "feat", 108 | "release": "minor" 109 | }, 110 | { 111 | "type": "fix", 112 | "release": "patch" 113 | }, 114 | { 115 | "type": "chore", 116 | "release": "patch" 117 | }, 118 | { 119 | "type": "docs", 120 | "release": "patch" 121 | }, 122 | { 123 | "type": "test", 124 | "release": "patch" 125 | }, 126 | { 127 | "scope": "no-release", 128 | "release": false 129 | } 130 | ] 131 | } 132 | ], 133 | [ 134 | "@semantic-release/release-notes-generator", 135 | { 136 | "preset": "conventionalcommits", 137 | "presetConfig": { 138 | "types": [ 139 | { 140 | "type": "feat", 141 | "section": "Features" 142 | }, 143 | { 144 | "type": "fix", 145 | "section": "Bug Fixes" 146 | }, 147 | { 148 | "type": "chore", 149 | "section": "Trivial Changes" 150 | }, 151 | { 152 | "type": "docs", 153 | "section": "Trivial Changes" 154 | }, 155 | { 156 | "type": "test", 157 | "section": "Tests" 158 | } 159 | ] 160 | } 161 | } 162 | ], 163 | "@semantic-release/changelog", 164 | "@semantic-release/npm", 165 | "@semantic-release/github", 166 | "@semantic-release/git" 167 | ] 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | // can be run in a browser with `polendina --runner=bare-sync --timeout 6000 --cleanup bench.js` 2 | // with additional dependencies for cborg installed here 3 | 4 | import assert from 'assert' 5 | import { garbage } from 'ipld-garbage' 6 | import { decode, encode } from '../cborg.js' 7 | import borc from 'borc' 8 | 9 | let writebuf = '' 10 | const write = process.stdout 11 | ? process.stdout.write.bind(process.stdout) 12 | : (str) => { 13 | writebuf += str 14 | if (str.endsWith('\n')) { 15 | console.log(writebuf.replace(/\n$/, '')) 16 | writebuf = '' 17 | } 18 | } 19 | 20 | function runWith (description, count, targetTime, size, options) { 21 | let borcDecoder = null 22 | const borcDecode = (bytes) => { 23 | if (!borcDecoder) { 24 | // account for initial allocation & setup time in benchmark 25 | borcDecoder = new borc.Decoder({ size: 10 * 1024 * 1024 }) 26 | } 27 | return borcDecoder.decodeAll(bytes)[0] 28 | } 29 | 30 | const fixtures = [] 31 | 32 | console.log(`${description} @ ${count.toLocaleString()}`) 33 | for (let i = 0; i < count; i++) { 34 | const obj = garbage(size, options) 35 | const cbyts = encode(obj) 36 | /* 37 | const bbyts = borc.encode(obj) 38 | if (Buffer.compare(Buffer.from(cbyts), bbyts) !== 0) { 39 | console.log(`mismatch for obj: ${JSON.stringify(obj)}`) 40 | console.log('\t', Buffer.from(cbyts).toString('hex')) 41 | console.log('\t', Buffer.from(bbyts).toString('hex')) 42 | } 43 | */ 44 | if (cbyts.length <= size * 2) { 45 | fixtures.push([obj, cbyts]) 46 | } 47 | } 48 | const avgSize = Math.round(fixtures.reduce((p, c) => p + c[1].length, 0) / fixtures.length) 49 | 50 | const enc = (encoder) => { 51 | for (const [obj, byts] of fixtures) { 52 | const ebyts = encoder(obj) 53 | if (byts.length !== ebyts.length) { 54 | throw new Error('bork') 55 | } 56 | } 57 | return fixtures.length 58 | } 59 | 60 | const bench = (bfn) => { 61 | const start = Date.now() 62 | let opcount = 0 63 | do { 64 | opcount += bfn() 65 | } while (Date.now() - start < targetTime) 66 | const ops = Math.round(opcount / ((Date.now() - start) / 1000)) 67 | return ops 68 | } 69 | 70 | const dec = (decoder) => { 71 | for (const [obj, byts] of fixtures) { 72 | const cobj = decoder(byts) 73 | if (obj != null && typeof obj === 'object') { 74 | assert.deepStrictEqual(Object.keys(cobj).length, Object.keys(obj).length) 75 | } else { 76 | assert.deepStrictEqual(obj, cobj) 77 | } 78 | } 79 | return fixtures.length 80 | } 81 | 82 | const cmp = (desc, cbfn, bofn) => { 83 | write(`\t${desc} (avg ${avgSize.toLocaleString()} b):`) 84 | const cborgOps = bench(cbfn) 85 | write(` cborg @ ${cborgOps.toLocaleString()} op/s`) 86 | const borcOps = bench(bofn) 87 | write(` / borc @ ${borcOps.toLocaleString()} op/s`) 88 | const percent = Math.round((cborgOps / borcOps) * 1000) / 10 89 | write(` = ${(percent).toLocaleString()} %\n`) 90 | return percent 91 | } 92 | 93 | return [ 94 | cmp('encode', () => enc(encode), () => enc(borc.encode)), 95 | cmp('decode', () => dec(decode), () => dec(borcDecode)) 96 | ] 97 | } 98 | 99 | const targetTime = 1000 100 | const accum = [] 101 | accum.push(runWith('rnd-100', 1000, targetTime, 100, { weights: { CID: 0 } })) 102 | accum.push(runWith('rnd-300', 1000, targetTime, 300, { weights: { CID: 0 } })) 103 | accum.push(runWith('rnd-nomap-300', 1000, targetTime, 300, { weights: { CID: 0, map: 0 } })) 104 | accum.push(runWith('rnd-nolist-300', 1000, targetTime, 300, { weights: { CID: 0, list: 0 } })) 105 | accum.push(runWith('rnd-nofloat-300', 1000, targetTime, 300, { weights: { CID: 0, float: 0 } })) 106 | accum.push(runWith('rnd-nomaj7-300', 1000, targetTime, 300, { weights: { CID: 0, float: 0, null: 0, boolean: 0 } })) 107 | accum.push(runWith('rnd-nostr-300', 1000, targetTime, 300, { weights: { CID: 0, string: 0, bytes: 0 } })) 108 | accum.push(runWith('rnd-nostrbyts-300', 1000, targetTime, 300, { weights: { CID: 0, string: 0 } })) 109 | accum.push(runWith('rnd-1000', 1000, targetTime, 1000, { weights: { CID: 0 } })) 110 | accum.push(runWith('rnd-2000', 1000, targetTime, 2000, { weights: { CID: 0 } })) 111 | accum.push(runWith('rnd-fil-100', 1000, targetTime, 100, { weights: { float: 0, map: 0, CID: 0 } })) 112 | accum.push(runWith('rnd-fil-300', 1000, targetTime, 300, { weights: { float: 0, map: 0, CID: 0 } })) 113 | accum.push(runWith('rnd-fil-500', 1000, targetTime, 500, { weights: { float: 0, map: 0, CID: 0 } })) 114 | accum.push(runWith('rnd-fil-1000', 1000, targetTime, 1000, { weights: { float: 0, map: 0, CID: 0 } })) 115 | accum.push(runWith('rnd-fil-2000', 1000, targetTime, 2000, { weights: { float: 0, map: 0, CID: 0 } })) 116 | console.log(`Avg encode: ${Math.round(accum.reduce((p, c) => p + c[0], 0) / accum.length).toLocaleString()} %`) 117 | console.log(`Avg decode: ${Math.round(accum.reduce((p, c) => p + c[1], 0) / accum.length).toLocaleString()} %`) 118 | -------------------------------------------------------------------------------- /bench/json.js: -------------------------------------------------------------------------------- 1 | // can be run in a browser with `polendina --runner=bare-sync --timeout 6000 --cleanup bench.js` 2 | // with additional dependencies for cborg installed here 3 | 4 | import assert from 'assert' 5 | import { garbage } from 'ipld-garbage' 6 | import { decode, encode } from '../lib/json/json.js' 7 | 8 | let writebuf = '' 9 | const write = process.stdout 10 | ? process.stdout.write.bind(process.stdout) 11 | : (str) => { 12 | writebuf += str 13 | if (str.endsWith('\n')) { 14 | console.log(writebuf.replace(/\n$/, '')) 15 | writebuf = '' 16 | } 17 | } 18 | 19 | const textEncoder = new TextEncoder() 20 | const textDecoder = new TextDecoder() 21 | 22 | function jencode (obj) { 23 | return textEncoder.encode(JSON.stringify(obj)) 24 | } 25 | 26 | function jdecode (buf) { 27 | return JSON.parse(textDecoder.decode(buf)) 28 | } 29 | 30 | function runWith (description, count, targetTime, size, options) { 31 | const fixtures = [] 32 | 33 | console.log(`${description} @ ${count.toLocaleString()}`) 34 | for (let i = 0; i < count; i++) { 35 | const obj = garbage(size, options) 36 | const cbyts = encode(obj) 37 | /* 38 | const jbyts = jencode(obj) 39 | if (Buffer.compare(Buffer.from(cbyts), jbyts) !== 0) { 40 | console.log(`mismatch for obj: ${JSON.stringify(obj)}`) 41 | console.log('\tc> ', Buffer.from(cbyts).toString('utf8')) 42 | console.log('\tj> ', Buffer.from(jbyts).toString('utf8')) 43 | } 44 | */ 45 | if (cbyts.length <= size * 2) { 46 | fixtures.push([obj, cbyts]) 47 | } 48 | } 49 | const avgSize = Math.round(fixtures.reduce((p, c) => p + c[1].length, 0) / fixtures.length) 50 | 51 | const enc = (encoder) => { 52 | for (const [obj, byts] of fixtures) { 53 | const ebyts = encoder(obj) 54 | if (byts.length !== ebyts.length) { 55 | throw new Error('bork') 56 | } 57 | } 58 | return fixtures.length 59 | } 60 | 61 | const bench = (bfn) => { 62 | const start = Date.now() 63 | let opcount = 0 64 | do { 65 | opcount += bfn() 66 | } while (Date.now() - start < targetTime) 67 | const ops = Math.round(opcount / ((Date.now() - start) / 1000)) 68 | return ops 69 | } 70 | 71 | const dec = (decoder) => { 72 | for (const [obj, byts] of fixtures) { 73 | let cobj 74 | try { 75 | cobj = decoder(byts) 76 | } catch (e) { 77 | console.log('Failed to decode:', Buffer.from(byts).toString('utf8')) 78 | throw e 79 | } 80 | if (obj != null && typeof obj === 'object') { 81 | assert.deepStrictEqual(Object.keys(cobj).length, Object.keys(obj).length) 82 | } else { 83 | assert.deepStrictEqual(obj, cobj) 84 | } 85 | } 86 | return fixtures.length 87 | } 88 | 89 | const cmp = (desc, cbfn, bofn) => { 90 | write(`\t${desc} (avg ${avgSize.toLocaleString()} b):`) 91 | const cborgOps = bench(cbfn) 92 | write(` cborg @ ${cborgOps.toLocaleString()} op/s`) 93 | const jOps = bench(bofn) 94 | write(` / JSON @ ${jOps.toLocaleString()} op/s`) 95 | const percent = Math.round((cborgOps / jOps) * 1000) / 10 96 | write(` = ${(percent).toLocaleString()} %\n`) 97 | return percent 98 | } 99 | 100 | return [ 101 | cmp('encode', () => enc(encode), () => enc(jencode)), 102 | cmp('decode', () => dec(decode), () => dec(jdecode)) 103 | ] 104 | } 105 | 106 | const targetTime = 1000 107 | const accum = [] 108 | accum.push(runWith('rnd-100', 1000, targetTime, 100, { weights: { CID: 0, bytes: 0 } })) 109 | accum.push(runWith('rnd-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0 } })) 110 | accum.push(runWith('rnd-nomap-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0, map: 0 } })) 111 | accum.push(runWith('rnd-nolist-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0, list: 0 } })) 112 | accum.push(runWith('rnd-nofloat-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0, float: 0 } })) 113 | accum.push(runWith('rnd-nomaj7-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0, float: 0, null: 0, boolean: 0 } })) 114 | accum.push(runWith('rnd-nostr-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0, string: 0 } })) 115 | accum.push(runWith('rnd-nostrbyts-300', 1000, targetTime, 300, { weights: { CID: 0, bytes: 0, string: 0 } })) 116 | accum.push(runWith('rnd-1000', 1000, targetTime, 1000, { weights: { CID: 0, bytes: 0 } })) 117 | accum.push(runWith('rnd-2000', 1000, targetTime, 2000, { weights: { CID: 0, bytes: 0 } })) 118 | accum.push(runWith('rnd-fil-100', 1000, targetTime, 100, { weights: { float: 0, map: 0, CID: 0, bytes: 0 } })) 119 | accum.push(runWith('rnd-fil-300', 1000, targetTime, 300, { weights: { float: 0, map: 0, CID: 0, bytes: 0 } })) 120 | accum.push(runWith('rnd-fil-500', 1000, targetTime, 500, { weights: { float: 0, map: 0, CID: 0, bytes: 0 } })) 121 | accum.push(runWith('rnd-fil-1000', 1000, targetTime, 1000, { weights: { float: 0, map: 0, CID: 0, bytes: 0 } })) 122 | accum.push(runWith('rnd-fil-2000', 1000, targetTime, 2000, { weights: { float: 0, map: 0, CID: 0, bytes: 0 } })) 123 | console.log(`Avg encode: ${Math.round(accum.reduce((p, c) => p + c[0], 0) / accum.length).toLocaleString()} %`) 124 | console.log(`Avg decode: ${Math.round(accum.reduce((p, c) => p + c[1], 0) / accum.length).toLocaleString()} %`) 125 | -------------------------------------------------------------------------------- /lib/diagnostic.js: -------------------------------------------------------------------------------- 1 | import { Tokeniser } from './decode.js' 2 | import { toHex, fromHex } from './byte-utils.js' 3 | import { uintBoundaries } from './0uint.js' 4 | 5 | const utf8Encoder = new TextEncoder() 6 | const utf8Decoder = new TextDecoder() 7 | 8 | /** 9 | * @param {Uint8Array} inp 10 | * @param {number} [width] 11 | */ 12 | function * tokensToDiagnostic (inp, width = 100) { 13 | const tokeniser = new Tokeniser(inp, { retainStringBytes: true, allowBigInt: true }) 14 | let pos = 0 15 | const indent = [] 16 | 17 | /** 18 | * @param {number} start 19 | * @param {number} length 20 | * @returns {string} 21 | */ 22 | const slc = (start, length) => { 23 | return toHex(inp.slice(pos + start, pos + start + length)) 24 | } 25 | 26 | while (!tokeniser.done()) { 27 | const token = tokeniser.next() 28 | let margin = ''.padStart(indent.length * 2, ' ') 29 | // @ts-ignore should be safe for decode 30 | let vLength = token.encodedLength - 1 31 | /** @type {string|number} */ 32 | let v = String(token.value) 33 | let outp = `${margin}${slc(0, 1)}` 34 | const str = token.type.name === 'bytes' || token.type.name === 'string' 35 | if (token.type.name === 'string') { 36 | v = v.length 37 | vLength -= v 38 | } else if (token.type.name === 'bytes') { 39 | v = token.value.length 40 | // @ts-ignore 41 | vLength -= v 42 | } 43 | 44 | let multilen 45 | switch (token.type.name) { 46 | case 'string': 47 | case 'bytes': 48 | case 'map': 49 | case 'array': 50 | // for bytes and string, we want to print out the length part of the value prefix if it 51 | // exists - it exists for short lengths (<24) but does for longer lengths 52 | multilen = token.type.name === 'string' ? utf8Encoder.encode(token.value).length : token.value.length 53 | if (multilen >= uintBoundaries[0]) { 54 | if (multilen < uintBoundaries[1]) { 55 | outp += ` ${slc(1, 1)}` 56 | } else if (multilen < uintBoundaries[2]) { 57 | outp += ` ${slc(1, 2)}` 58 | /* c8 ignore next 5 */ 59 | } else if (multilen < uintBoundaries[3]) { // sus 60 | outp += ` ${slc(1, 4)}` 61 | } else if (multilen < uintBoundaries[4]) { // orly? 62 | outp += ` ${slc(1, 8)}` 63 | } 64 | } 65 | break 66 | default: 67 | // print the value if it's not compacted into the first byte 68 | outp += ` ${slc(1, vLength)}` 69 | break 70 | } 71 | 72 | outp = outp.padEnd(width / 2, ' ') 73 | outp += `# ${margin}${token.type.name}` 74 | if (token.type.name !== v) { 75 | outp += `(${v})` 76 | } 77 | yield outp 78 | 79 | if (str) { 80 | let asString = token.type.name === 'string' 81 | margin += ' ' 82 | let repr = asString ? utf8Encoder.encode(token.value) : token.value 83 | if (asString && token.byteValue !== undefined) { 84 | if (repr.length !== token.byteValue.length) { 85 | // bail on printing this as a string, it's probably not utf8, so treat it as bytes 86 | // (you can probably blame a Go programmer for this) 87 | repr = token.byteValue 88 | asString = false 89 | } 90 | } 91 | const wh = ((width / 2) - margin.length - 1) / 2 92 | let snip = 0 93 | while (repr.length - snip > 0) { 94 | const piece = repr.slice(snip, snip + wh) 95 | snip += piece.length 96 | const st = asString 97 | ? utf8Decoder.decode(piece) 98 | : piece.reduce((/** @type {string} */ p, /** @type {number} */ c) => { 99 | if (c < 0x20 || (c >= 0x7f && c < 0xa1) || c === 0xad) { 100 | return `${p}\\x${c.toString(16).padStart(2, '0')}` 101 | } 102 | return `${p}${String.fromCharCode(c)}` 103 | }, '') 104 | yield `${margin}${toHex(piece)}`.padEnd(width / 2, ' ') + `# ${margin}"${st}"` 105 | } 106 | } 107 | 108 | if (indent.length) { 109 | indent[indent.length - 1]-- 110 | } 111 | if (!token.type.terminal) { 112 | switch (token.type.name) { 113 | case 'map': 114 | indent.push(token.value * 2) 115 | break 116 | case 'array': 117 | indent.push(token.value) 118 | break 119 | // TODO: test tags .. somehow 120 | /* c8 ignore next 5 */ 121 | case 'tag': 122 | indent.push(1) 123 | break 124 | default: 125 | throw new Error(`Unknown token type '${token.type.name}'`) 126 | } 127 | } 128 | while (indent.length && indent[indent.length - 1] <= 0) { 129 | indent.pop() 130 | } 131 | // @ts-ignore it should be set on a decode operation 132 | pos += token.encodedLength 133 | } 134 | } 135 | 136 | /** 137 | * Convert an input string formatted as CBOR diagnostic output into binary CBOR form. 138 | * @param {string} input 139 | * @returns {Uint8Array} 140 | */ 141 | function fromDiag (input) { 142 | /* c8 ignore next 3 */ 143 | if (typeof input !== 'string') { 144 | throw new TypeError('Expected string input') 145 | } 146 | input = input.replace(/#.*?$/mg, '').replace(/[\s\r\n]+/mg, '') 147 | /* c8 ignore next 3 */ 148 | if (/[^a-f0-9]/i.test(input)) { 149 | throw new TypeError('Input string was not CBOR diagnostic format') 150 | } 151 | return fromHex(input) 152 | } 153 | 154 | export { tokensToDiagnostic, fromDiag } 155 | -------------------------------------------------------------------------------- /example-bytestrings.js: -------------------------------------------------------------------------------- 1 | /* 2 | RFC 8746 defines a set of tags to use for typed arrays. Out of the box, cborg doesn't care about 3 | tags and just squashes all concerns around byte arrays to Uint8Array with major type 2. This is 4 | fine for most use cases, but it is lossy, you can't round-trip and retain your original type. 5 | 6 | This example shows how to use cborg to round-trip a typed array with tags. 7 | 8 | https://www.rfc-editor.org/rfc/rfc8746.html 9 | */ 10 | 11 | import { encode, decode, Token, Tokenizer, Type } from 'cborg.js' 12 | 13 | const tagUint8Array = 64 14 | const tagUint64Array = 71 15 | // etc... see https://www.rfc-editor.org/rfc/rfc8746.html#name-iana-considerations 16 | 17 | /* ENCODERS */ 18 | 19 | /** 20 | * @param {any} obj 21 | * @returns {[Token]} 22 | */ 23 | function uint8ArrayEncoder (obj) { 24 | if (!(obj instanceof Uint8Array)) { 25 | throw new Error('expected Uint8Array') 26 | } 27 | return [ 28 | new Token(Type.tag, tagUint8Array), 29 | new Token(Type.bytes, obj) 30 | ] 31 | } 32 | 33 | /** 34 | * @param {any} obj 35 | * @returns {[Token]} 36 | */ 37 | function uint64ArrayEncoder (obj) { 38 | if (!(obj instanceof BigUint64Array)) { 39 | throw new Error('expected BigUint64Array') 40 | } 41 | return [ 42 | new Token(Type.tag, tagUint64Array), 43 | // BigUint64Array to a Uint8Array, but we have to pay attention to the possibility of it being 44 | // a view of a larger ArrayBuffer. 45 | new Token(Type.bytes, new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength)) 46 | ] 47 | } 48 | 49 | // etc... 50 | 51 | const typeEncoders = { 52 | Uint8Array: uint8ArrayEncoder, 53 | BigUint64Array: uint64ArrayEncoder 54 | } 55 | 56 | /* DECODERS */ 57 | 58 | /** 59 | * @param {ArrayBuffer} bytes 60 | * @returns {any} 61 | */ 62 | function uint8ArrayDecoder (bytes) { 63 | if (!(bytes instanceof ArrayBuffer)) { 64 | throw new Error('expected ArrayBuffer') 65 | } 66 | return new Uint8Array(bytes) 67 | } 68 | 69 | /** 70 | * @param {ArrayBuffer} bytes 71 | * @returns {any} 72 | */ 73 | function uint64ArrayDecoder (bytes) { 74 | if (!(bytes instanceof ArrayBuffer)) { 75 | throw new Error('expected ArrayBuffer') 76 | } 77 | return new BigUint64Array(bytes) 78 | } 79 | 80 | // etc... 81 | 82 | const tags = [] 83 | tags[tagUint8Array] = uint8ArrayDecoder 84 | tags[tagUint64Array] = uint64ArrayDecoder 85 | 86 | /* TOKENIZER */ 87 | 88 | // We have to deal with the fact that cborg talks in Uint8Arrays but we now want it to treat major 2 89 | // as ArrayBuffers, so we have to transform the token stream to replace the Uint8Array with an 90 | // ArrayBuffer. 91 | 92 | class ArrayBufferTransformingTokeniser extends Tokenizer { 93 | next () { 94 | const nextToken = super.next() 95 | if (nextToken.type === Type.bytes) { 96 | // Transform the (assumed) Uint8Array value to an ArrayBuffer of the same bytes, note though 97 | // that all tags we care about are going to be , so we're also transforming those 98 | // into ArrayBuffers, so our tag decoders need to also assume they are getting ArrayBuffers 99 | // now. An alternative would be to watch the token stream for and not transform the next 100 | // token if it's , but that's a bit more complicated for demo purposes. 101 | nextToken.value = nextToken.value.buffer 102 | } 103 | return nextToken 104 | } 105 | } 106 | 107 | // Optional: a new decode() wrapper, mainly so we don't have to deal with the complications of\ 108 | // instantiating a Tokenizer which needs both data and the options. 109 | function byteStringDecoder (data, options) { 110 | options = Object.assign({}, options, { 111 | tags, 112 | tokenizer: new ArrayBufferTransformingTokeniser(data, options) 113 | }) 114 | return decode(data, options) 115 | } 116 | 117 | /* ROUND-TRIP */ 118 | 119 | const original = { 120 | u8: new Uint8Array([1, 2, 3, 4, 5]), 121 | u64: new BigUint64Array([10000000000000000n, 20000000000000000n, 30000000000000000n, 40000000000000000n, 50000000000000000n]), 122 | ab: new Uint8Array([6, 7, 8, 9, 10]).buffer 123 | } 124 | 125 | const encoded = encode(original, { typeEncoders }) 126 | 127 | const decoded = byteStringDecoder(encoded) 128 | 129 | console.log('Original:', original) 130 | console.log('Encoded:', Buffer.from(encoded).toString('hex')) // excuse the Buffer, sorry browser peeps 131 | console.log('Decoded:', decoded) 132 | 133 | /* Output: 134 | 135 | Original: { 136 | u8: Uint8Array(5) [ 1, 2, 3, 4, 5 ], 137 | u64: BigUint64Array(5) [ 138 | 10000000000000000n, 139 | 20000000000000000n, 140 | 30000000000000000n, 141 | 40000000000000000n, 142 | 50000000000000000n 143 | ], 144 | ab: ArrayBuffer { [Uint8Contents]: <06 07 08 09 0a>, byteLength: 5 } 145 | } 146 | Encoded: a362616245060708090a627538d84045010203040563753634d84758280000c16ff2862300000082dfe40d47000000434fd7946a00000004bfc91b8e000000c52ebca2b100 147 | Decoded: { 148 | ab: ArrayBuffer { [Uint8Contents]: <06 07 08 09 0a>, byteLength: 5 }, 149 | u8: Uint8Array(5) [ 1, 2, 3, 4, 5 ], 150 | u64: BigUint64Array(5) [ 151 | 10000000000000000n, 152 | 20000000000000000n, 153 | 30000000000000000n, 154 | 40000000000000000n, 155 | 50000000000000000n 156 | ] 157 | } 158 | 159 | */ 160 | 161 | /* Diagnostic: 162 | 163 | $ cborg hex2diag a362616245060708090a627538d84045010203040563753634d84758280000c16ff2862300000082dfe40d47000000434fd7946a00000004bfc91b8e000000c52ebca2b100 164 | a3 # map(3) 165 | 62 # string(2) 166 | 6162 # "ab" 167 | 45 # bytes(5) 168 | 060708090a # "\x06\x07\x08\x09\x0a" 169 | 62 # string(2) 170 | 7538 # "u8" 171 | d8 40 # tag(64) 172 | 45 # bytes(5) 173 | 0102030405 # "\x01\x02\x03\x04\x05" 174 | 63 # string(3) 175 | 753634 # "u64" 176 | d8 47 # tag(71) 177 | 58 28 # bytes(40) 178 | 0000c16ff2862300000082dfe40d47000000434fd7 # "\x00\x00Áoò\x86#\x00\x00\x00\x82ßä\x0dG\x00\x00\x00CO×" 179 | 946a00000004bfc91b8e000000c52ebca2b100 # "\x94j\x00\x00\x00\x04¿É\x1b\x8e\x00\x00\x00Å.¼¢±\x00 180 | */ 181 | -------------------------------------------------------------------------------- /test/test-3string.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode, encode } from '../cborg.js' 6 | import { fromHex, toHex } from '../lib/byte-utils.js' 7 | 8 | const { assert } = chai 9 | 10 | // some from https://github.com/PJK/libcbor 11 | 12 | const fixtures = [ 13 | { data: '60', expected: '', type: 'string' }, 14 | { data: '6161', expected: 'a', type: 'string' }, 15 | { data: '780161', expected: 'a', type: 'string', strict: false }, 16 | { 17 | data: '6c48656c6c6f20776f726c6421', 18 | expected: 'Hello world!', 19 | type: 'string' 20 | }, 21 | { 22 | data: '6fc48c6175657320c39f76c49b746521', 23 | expected: 'Čaues ßvěte!', 24 | type: 'string' 25 | }, 26 | { 27 | data: '78964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e', 28 | expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.', 29 | type: 'string', 30 | label: 'long string, 8-bit length' 31 | }, 32 | { 33 | data: '7900964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e', 34 | expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.', 35 | type: 'string', 36 | label: 'long string, 16-bit length', 37 | strict: false 38 | }, 39 | { 40 | data: '7a000000964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e', 41 | expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.', 42 | type: 'string', 43 | label: 'long string, 32-bit length', 44 | strict: false 45 | }, 46 | { 47 | data: '7b00000000000000964c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e', 48 | expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mi tellus, iaculis nec vestibulum quis, fermentum non felis. Maecenas ut justo posuere.', 49 | type: 'string', 50 | label: 'long string, 64-bit length', 51 | strict: false 52 | } 53 | ] 54 | 55 | // fill up byte arrays converted to strings so we can validate in strict mode, 56 | // the minimal size for each excluding 64-bit because 4G is just too big 57 | ;(() => { 58 | function rnd (length) { 59 | const sa = [] 60 | let l = 0 61 | while (l < length) { 62 | // some unicode character, unless we're near the end and want to fill up exactly so 63 | // we need to pad with ascii 64 | const ascii = (length - l) < 3 65 | const base = ascii ? 32 : 0x1f000 66 | const max = ascii ? 126 : 0x1ff00 67 | const cc = Math.floor(Math.random() * (max - base)) + base 68 | const s = String.fromCharCode(cc) 69 | l += (new TextEncoder().encode(s)).length 70 | sa.push(s) 71 | } 72 | return sa.join('') 73 | } 74 | 75 | const expected16 = rnd(256) 76 | fixtures.push({ 77 | data: new Uint8Array([...fromHex('790100'), ...(new TextEncoder().encode(expected16))]), 78 | expected: expected16, 79 | type: 'string', 80 | label: 'long string, 16-bit length strict-compat' 81 | }) 82 | 83 | const expected32 = rnd(65536) 84 | fixtures.push({ 85 | data: new Uint8Array([...fromHex('7a00010000'), ...(new TextEncoder().encode(expected32))]), 86 | expected: expected32, 87 | type: 'string', 88 | label: 'long string, 32-bit length strict-compat' 89 | }) 90 | })() 91 | 92 | describe('string', () => { 93 | describe('decode', () => { 94 | for (const fixture of fixtures) { 95 | const data = fromHex(fixture.data) 96 | it(`should decode ${fixture.type}=${fixture.label || fixture.expected}`, () => { 97 | let actual = decode(data) 98 | assert.strictEqual(actual, fixture.expected, `decode ${fixture.type}`) 99 | if (fixture.strict === false) { 100 | assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)') 101 | } else { 102 | actual = decode(data, { strict: true }) 103 | assert.strictEqual(actual, fixture.expected, `decode ${fixture.type} strict`) 104 | } 105 | }) 106 | 107 | it('should fail to decode very large length', () => { 108 | assert.throws( 109 | () => decode(fromHex('7ba5f702b3a5f702b34c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20446f6e6563206d692074656c6c75732c20696163756c6973206e656320766573746962756c756d20717569732c206665726d656e74756d206e6f6e2066656c69732e204d616563656e6173207574206a7573746f20706f73756572652e')), 110 | /CBOR decode error: 64-bit integer string lengths not supported/) 111 | }) 112 | } 113 | }) 114 | 115 | describe('encode', () => { 116 | for (const fixture of fixtures) { 117 | if (fixture.data.length >= 100000000) { 118 | it.skip(`(TODO) skipping encode of very large string ${fixture.type}=${fixture.label || fixture.expected}`, () => {}) 119 | continue 120 | } 121 | 122 | const data = fixture.expected 123 | const expectedHex = toHex(fixture.data) 124 | 125 | it(`should encode ${fixture.type}=${fixture.label || fixture.expected}`, () => { 126 | if (fixture.unsafe) { 127 | assert.throws(() => encode(data), Error, /^CBOR encode error: number too large to encode \(-\d+\)$/) 128 | } else if (fixture.strict === false) { 129 | assert.notStrictEqual(toHex(encode(data)), expectedHex, `encode ${fixture.type} !strict`) 130 | } else { 131 | assert.strictEqual(toHex(encode(data)), expectedHex, `encode ${fixture.type}`) 132 | } 133 | }) 134 | } 135 | }) 136 | }) 137 | -------------------------------------------------------------------------------- /lib/bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import process from 'process' 4 | import { decode, encode } from '../cborg.js' 5 | import { tokensToDiagnostic, fromDiag } from './diagnostic.js' 6 | import { fromHex as _fromHex, toHex } from './byte-utils.js' 7 | 8 | /** 9 | * @param {number} code 10 | */ 11 | function usage (code) { 12 | console.error('Usage: cborg ') 13 | console.error('Valid commands:') 14 | console.error('\tbin2diag [--width ] [binary input]') 15 | console.error('\tbin2hex [binary input]') 16 | console.error('\tbin2json [--pretty] [binary input]') 17 | console.error('\tdiag2bin [diagnostic input]') 18 | console.error('\tdiag2hex [diagnostic input]') 19 | console.error('\tdiag2json [--pretty] [diagnostic input]') 20 | console.error('\thex2bin [hex input]') 21 | console.error('\thex2diag [--width ] [hex input]') 22 | console.error('\thex2json [--pretty] [hex input]') 23 | console.error('\tjson2bin \'[json input]\'') 24 | console.error('\tjson2diag [--width ] \'[json input]\'') 25 | console.error('\tjson2hex \'[json input]\'') 26 | console.error('Input may either be supplied as an argument or piped via stdin') 27 | process.exit(code || 0) 28 | } 29 | 30 | async function fromStdin () { 31 | const chunks = [] 32 | for await (const chunk of process.stdin) { 33 | chunks.push(chunk) 34 | } 35 | return Buffer.concat(chunks) 36 | } 37 | 38 | /** 39 | * @param {string} str 40 | * @returns {Uint8Array} 41 | */ 42 | function fromHex (str) { 43 | str = str.replace(/\r?\n/g, '') // let's be charitable 44 | /* c8 ignore next 3 */ 45 | if (!(/^([0-9a-f]{2})*$/i).test(str)) { 46 | throw new Error('Input string is not hexadecimal format') 47 | } 48 | return _fromHex(str) 49 | } 50 | 51 | function argvPretty () { 52 | const argv = process.argv.filter((s) => s !== '--pretty') 53 | const pretty = argv.length !== process.argv.length 54 | return { argv, pretty } 55 | } 56 | 57 | function argvWidth () { 58 | const widthIndex = process.argv.findIndex((s) => s === '--width') 59 | if (widthIndex <= 0 || widthIndex + 1 >= process.argv.length) { 60 | return { argv: process.argv.filter((s) => s !== '--width'), width: undefined } 61 | } 62 | const width = parseInt(process.argv[widthIndex + 1], 10) 63 | if (!width || width < 20) { 64 | return { argv: process.argv.filter((s) => s !== '--width'), width: undefined } 65 | } 66 | const argv = process.argv 67 | argv.splice(widthIndex, 2) 68 | return { argv, width } 69 | } 70 | 71 | async function run () { 72 | const cmd = process.argv[2] 73 | 74 | switch (cmd) { 75 | case 'help': { 76 | return usage(0) 77 | } 78 | 79 | case 'bin2diag': { 80 | /* c8 ignore next 1 */ 81 | const { argv, width } = argvWidth() 82 | const bin = argv.length < 4 ? (await fromStdin()) : new TextEncoder().encode(argv[3]) 83 | for (const line of tokensToDiagnostic(bin, width)) { 84 | console.log(line) 85 | } 86 | return 87 | } 88 | 89 | case 'bin2hex': { 90 | // this is really nothing to do with cbor.. just handy 91 | /* c8 ignore next 1 */ 92 | const bin = process.argv.length < 4 ? (await fromStdin()) : new TextEncoder().encode(process.argv[3]) 93 | return console.log(toHex(bin)) 94 | } 95 | 96 | case 'bin2json': { 97 | const { argv, pretty } = argvPretty() 98 | /* c8 ignore next 1 */ 99 | const bin = argv.length < 4 ? (await fromStdin()) : new TextEncoder().encode(argv[3]) 100 | return console.log(JSON.stringify(decode(bin), undefined, pretty ? 2 : undefined)) 101 | } 102 | 103 | case 'diag2bin': { 104 | // no coverage on windows for non-stdin input 105 | /* c8 ignore next 1 */ 106 | const bin = fromDiag(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]) 107 | return process.stdout.write(bin) 108 | } 109 | 110 | case 'diag2hex': { 111 | // no coverage on windows for non-stdin input 112 | /* c8 ignore next 1 */ 113 | const bin = fromDiag(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]) 114 | return console.log(toHex(bin)) 115 | } 116 | 117 | case 'diag2json': { 118 | const { argv, pretty } = argvPretty() 119 | // no coverage on windows for non-stdin input 120 | /* c8 ignore next 1 */ 121 | const bin = fromDiag(argv.length < 4 ? (await fromStdin()).toString() : argv[3]) 122 | return console.log(JSON.stringify(decode(bin), undefined, pretty ? 2 : undefined)) 123 | } 124 | 125 | case 'hex2bin': { 126 | // this is really nothing to do with cbor.. just handy 127 | const bin = fromHex(process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3]) 128 | return process.stdout.write(bin) 129 | } 130 | 131 | case 'hex2diag': { 132 | const { argv, width } = argvWidth() 133 | const bin = fromHex(argv.length < 4 ? (await fromStdin()).toString() : argv[3]) 134 | for (const line of tokensToDiagnostic(bin, width)) { 135 | console.log(line) 136 | } 137 | return 138 | } 139 | 140 | case 'hex2json': { 141 | const { argv, pretty } = argvPretty() 142 | const bin = fromHex(argv.length < 4 ? (await fromStdin()).toString() : argv[3]) 143 | return console.log(JSON.stringify(decode(bin), undefined, pretty ? 2 : undefined)) 144 | } 145 | 146 | case 'json2bin': { 147 | const inp = process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3] 148 | const obj = JSON.parse(inp) 149 | return process.stdout.write(encode(obj)) 150 | } 151 | 152 | case 'json2diag': { 153 | const { argv, width } = argvWidth() 154 | const inp = argv.length < 4 ? (await fromStdin()).toString() : argv[3] 155 | const obj = JSON.parse(inp) 156 | for (const line of tokensToDiagnostic(encode(obj), width)) { 157 | console.log(line) 158 | } 159 | return 160 | } 161 | 162 | case 'json2hex': { 163 | const inp = process.argv.length < 4 ? (await fromStdin()).toString() : process.argv[3] 164 | const obj = JSON.parse(inp) 165 | return console.log(toHex(encode(obj))) 166 | } 167 | 168 | default: { // no, or unknown cmd 169 | // this is a dirty hack to allow import of this package by the tests 170 | // for inclusion in ipjs bundling, but to silently ignore it so we don't 171 | // print usage and exit(1). 172 | if (process.argv.findIndex((a) => a.endsWith('mocha')) === -1) { 173 | if (cmd) { 174 | console.error(`Unknown command: '${cmd}'`) 175 | } 176 | usage(1) 177 | } 178 | } 179 | } 180 | } 181 | 182 | run().catch((err) => { 183 | /* c8 ignore next 2 */ 184 | console.error(err) 185 | process.exit(1) 186 | }) 187 | 188 | // for ipjs, to get it to compile 189 | export default true 190 | -------------------------------------------------------------------------------- /lib/decode.js: -------------------------------------------------------------------------------- 1 | import { decodeErrPrefix } from './common.js' 2 | import { Type } from './token.js' 3 | import { jump, quick } from './jump.js' 4 | 5 | /** 6 | * @typedef {import('./token.js').Token} Token 7 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 8 | * @typedef {import('../interface').DecodeTokenizer} DecodeTokenizer 9 | */ 10 | 11 | const defaultDecodeOptions = { 12 | strict: false, 13 | allowIndefinite: true, 14 | allowUndefined: true, 15 | allowBigInt: true 16 | } 17 | 18 | /** 19 | * @implements {DecodeTokenizer} 20 | */ 21 | class Tokeniser { 22 | /** 23 | * @param {Uint8Array} data 24 | * @param {DecodeOptions} options 25 | */ 26 | constructor (data, options = {}) { 27 | this._pos = 0 28 | this.data = data 29 | this.options = options 30 | } 31 | 32 | pos () { 33 | return this._pos 34 | } 35 | 36 | done () { 37 | return this._pos >= this.data.length 38 | } 39 | 40 | next () { 41 | const byt = this.data[this._pos] 42 | let token = quick[byt] 43 | if (token === undefined) { 44 | const decoder = jump[byt] 45 | /* c8 ignore next 4 */ 46 | // if we're here then there's something wrong with our jump or quick lists! 47 | if (!decoder) { 48 | throw new Error(`${decodeErrPrefix} no decoder for major type ${byt >>> 5} (byte 0x${byt.toString(16).padStart(2, '0')})`) 49 | } 50 | const minor = byt & 31 51 | token = decoder(this.data, this._pos, minor, this.options) 52 | } 53 | // @ts-ignore we get to assume encodedLength is set (crossing fingers slightly) 54 | this._pos += token.encodedLength 55 | return token 56 | } 57 | } 58 | 59 | const DONE = Symbol.for('DONE') 60 | const BREAK = Symbol.for('BREAK') 61 | 62 | /** 63 | * @param {Token} token 64 | * @param {DecodeTokenizer} tokeniser 65 | * @param {DecodeOptions} options 66 | * @returns {any|BREAK|DONE} 67 | */ 68 | function tokenToArray (token, tokeniser, options) { 69 | const arr = [] 70 | for (let i = 0; i < token.value; i++) { 71 | const value = tokensToObject(tokeniser, options) 72 | if (value === BREAK) { 73 | if (token.value === Infinity) { 74 | // normal end to indefinite length array 75 | break 76 | } 77 | throw new Error(`${decodeErrPrefix} got unexpected break to lengthed array`) 78 | } 79 | if (value === DONE) { 80 | throw new Error(`${decodeErrPrefix} found array but not enough entries (got ${i}, expected ${token.value})`) 81 | } 82 | arr[i] = value 83 | } 84 | return arr 85 | } 86 | 87 | /** 88 | * @param {Token} token 89 | * @param {DecodeTokenizer} tokeniser 90 | * @param {DecodeOptions} options 91 | * @returns {any|BREAK|DONE} 92 | */ 93 | function tokenToMap (token, tokeniser, options) { 94 | const useMaps = options.useMaps === true 95 | const obj = useMaps ? undefined : {} 96 | const m = useMaps ? new Map() : undefined 97 | for (let i = 0; i < token.value; i++) { 98 | const key = tokensToObject(tokeniser, options) 99 | if (key === BREAK) { 100 | if (token.value === Infinity) { 101 | // normal end to indefinite length map 102 | break 103 | } 104 | throw new Error(`${decodeErrPrefix} got unexpected break to lengthed map`) 105 | } 106 | if (key === DONE) { 107 | throw new Error(`${decodeErrPrefix} found map but not enough entries (got ${i} [no key], expected ${token.value})`) 108 | } 109 | if (useMaps !== true && typeof key !== 'string') { 110 | throw new Error(`${decodeErrPrefix} non-string keys not supported (got ${typeof key})`) 111 | } 112 | if (options.rejectDuplicateMapKeys === true) { 113 | // @ts-ignore 114 | if ((useMaps && m.has(key)) || (!useMaps && (key in obj))) { 115 | throw new Error(`${decodeErrPrefix} found repeat map key "${key}"`) 116 | } 117 | } 118 | const value = tokensToObject(tokeniser, options) 119 | if (value === DONE) { 120 | throw new Error(`${decodeErrPrefix} found map but not enough entries (got ${i} [no value], expected ${token.value})`) 121 | } 122 | if (useMaps) { 123 | // @ts-ignore TODO reconsider this .. maybe needs to be strict about key types 124 | m.set(key, value) 125 | } else { 126 | // @ts-ignore TODO reconsider this .. maybe needs to be strict about key types 127 | obj[key] = value 128 | } 129 | } 130 | // @ts-ignore c'mon man 131 | return useMaps ? m : obj 132 | } 133 | 134 | /** 135 | * @param {DecodeTokenizer} tokeniser 136 | * @param {DecodeOptions} options 137 | * @returns {any|BREAK|DONE} 138 | */ 139 | function tokensToObject (tokeniser, options) { 140 | // should we support array as an argument? 141 | // check for tokenIter[Symbol.iterator] and replace tokenIter with what that returns? 142 | if (tokeniser.done()) { 143 | return DONE 144 | } 145 | 146 | const token = tokeniser.next() 147 | 148 | if (token.type === Type.break) { 149 | return BREAK 150 | } 151 | 152 | if (token.type.terminal) { 153 | return token.value 154 | } 155 | 156 | if (token.type === Type.array) { 157 | return tokenToArray(token, tokeniser, options) 158 | } 159 | 160 | if (token.type === Type.map) { 161 | return tokenToMap(token, tokeniser, options) 162 | } 163 | 164 | if (token.type === Type.tag) { 165 | if (options.tags && typeof options.tags[token.value] === 'function') { 166 | const tagged = tokensToObject(tokeniser, options) 167 | return options.tags[token.value](tagged) 168 | } 169 | throw new Error(`${decodeErrPrefix} tag not supported (${token.value})`) 170 | } 171 | /* c8 ignore next */ 172 | throw new Error('unsupported') 173 | } 174 | 175 | /** 176 | * @param {Uint8Array} data 177 | * @param {DecodeOptions} [options] 178 | * @returns {[any, Uint8Array]} 179 | */ 180 | function decodeFirst (data, options) { 181 | if (!(data instanceof Uint8Array)) { 182 | throw new Error(`${decodeErrPrefix} data to decode must be a Uint8Array`) 183 | } 184 | options = Object.assign({}, defaultDecodeOptions, options) 185 | const tokeniser = options.tokenizer || new Tokeniser(data, options) 186 | const decoded = tokensToObject(tokeniser, options) 187 | if (decoded === DONE) { 188 | throw new Error(`${decodeErrPrefix} did not find any content to decode`) 189 | } 190 | if (decoded === BREAK) { 191 | throw new Error(`${decodeErrPrefix} got unexpected break`) 192 | } 193 | return [decoded, data.subarray(tokeniser.pos())] 194 | } 195 | 196 | /** 197 | * @param {Uint8Array} data 198 | * @param {DecodeOptions} [options] 199 | * @returns {any} 200 | */ 201 | function decode (data, options) { 202 | const [decoded, remainder] = decodeFirst(data, options) 203 | if (remainder.length > 0) { 204 | throw new Error(`${decodeErrPrefix} too many terminals, data makes no sense`) 205 | } 206 | return decoded 207 | } 208 | 209 | export { Tokeniser, tokensToObject, decode, decodeFirst } 210 | -------------------------------------------------------------------------------- /test/test-7float.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | import * as chai from 'chai' 4 | 5 | import { decode, encode } from '../cborg.js' 6 | import { fromHex, toHex } from '../lib/byte-utils.js' 7 | 8 | const { assert } = chai 9 | 10 | const fixtures = [ 11 | { data: '8601f5f4f6f720', expected: [1, true, false, null, undefined, -1], type: 'array of float specials' }, 12 | { data: 'f93800', expected: 0.5, type: 'float16' }, 13 | { data: 'f9b800', expected: -0.5, type: 'float16' }, 14 | { data: 'fa33c00000', expected: 8.940696716308594e-08, type: 'float32' }, 15 | { data: 'fab3c00000', expected: -8.940696716308594e-08, type: 'float32' }, 16 | { data: 'fb3ff199999999999a', expected: 1.1, type: 'float64' }, 17 | { data: 'fbbff199999999999a', expected: -1.1, type: 'float64' }, 18 | { data: 'fb3ff1c71c71c71c72', expected: 1.11111111111111111111111111111, type: 'float64' }, // eslint-disable-line 19 | { data: 'fb0000000000000002', expected: 1e-323, type: 'float64' }, 20 | { data: 'fb8000000000000002', expected: -1e-323, type: 'float64' }, 21 | { data: 'fb3fefffffffffffff', expected: 0.9999999999999999, type: 'float64' }, 22 | { data: 'fbbfefffffffffffff', expected: -0.9999999999999999, type: 'float64' }, 23 | { data: 'f97c00', expected: Infinity, type: 'Infinity' }, // special CBOR token for -Infinity 24 | { data: 'fb7ff0000000000000', expected: Infinity, type: 'Infinity', strict: false }, // an IEEE 754 representation of Infinity 25 | { data: 'f9fc00', expected: -Infinity, type: '-Infinity' }, // special CBOR token for -Infinity 26 | { data: 'fbfff0000000000000', expected: -Infinity, type: '-Infinity', strict: false }, // an IEEE 754 representation of Infinity 27 | { data: 'f97e00', expected: NaN, type: 'NaN' }, // special CBOR token for NaN 28 | { data: 'f97ff8', expected: NaN, type: 'NaN', strict: false }, // one of the many IEEE 754 representations of NaN 29 | { data: 'fa7ff80000', expected: NaN, type: 'NaN', strict: false }, 30 | { data: 'fb7ff8000000000000', expected: NaN, type: 'NaN', strict: false }, 31 | { data: 'fb7ff8cafedeadbeef', expected: NaN, type: 'NaN', strict: false }, // yep, that's NaN too 32 | { data: 'fb40f4241a31a5a515', expected: 82497.63712086187, type: 'float64' } 33 | ] 34 | 35 | describe('float', () => { 36 | describe('decode', () => { 37 | for (const fixture of fixtures) { 38 | const data = fromHex(fixture.data) 39 | it(`should decode ${fixture.type}=${fixture.expected}`, () => { 40 | assert.deepStrictEqual(decode(data), fixture.expected, `decode ${fixture.type}`) 41 | assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${fixture.type}`) 42 | }) 43 | } 44 | }) 45 | 46 | it('error', () => { 47 | // minor number 28, too high for uint 48 | assert.throws(() => decode(fromHex('f80000')), Error, 'simple values are not supported') 49 | assert.throws(() => decode(fromHex('f900')), Error, 'not enough data for float16') 50 | assert.throws(() => decode(fromHex('fa0000')), Error, 'not enough data for float32') 51 | assert.throws(() => decode(fromHex('fb00000000')), Error, 'not enough data for float64') 52 | }) 53 | 54 | describe('encode', () => { 55 | for (const fixture of fixtures) { 56 | if (fixture.strict !== false) { 57 | it(`should encode ${fixture.type}=${fixture.expected}`, () => { 58 | assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type}`) 59 | }) 60 | } 61 | } 62 | }) 63 | 64 | describe('encode float64', () => { 65 | for (const fixture of fixtures) { 66 | if (fixture.type.startsWith('float')) { 67 | it(`should encode ${fixture.type}=${fixture.expected}`, () => { 68 | const encoded = encode(fixture.expected, { float64: true }) 69 | assert.strictEqual(encoded.length, 9) // always encode as 9 bytes, regardless of size 70 | assert.strictEqual(encoded[0], 0xfb) 71 | assert.strictEqual(decode(encoded), fixture.expected, `encode float64 ${fixture.type}`) 72 | }) 73 | } 74 | } 75 | }) 76 | 77 | describe('roundtrip', () => { 78 | for (const fixture of fixtures) { 79 | if (!fixture.unsafe && fixture.strict !== false) { 80 | it(`should roundtrip ${fixture.type}=${fixture.expected}`, () => { 81 | assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${fixture.type}`) 82 | }) 83 | } 84 | } 85 | }) 86 | 87 | describe('specials', () => { 88 | // This is a bit of a hack, the CBOR is invalid because it's a standard fixed-length array 89 | // followed by a BREAK, which should normally error ("too many terminals"), but we want to 90 | // exercise the allowIndefinite switch in the major-7 decode and it should error before it 91 | // even gets to looking at terminals and whether the tokens make sense. 92 | it('indefinite length switch fails on BREAK', () => { 93 | // sanity check, BREAK doesn't belong there 94 | assert.throws(() => decode(Uint8Array.from([131, 1, 2, 0xff])), /unexpected break to lengthed array/) 95 | // throw earlier because we're disallowing BREAK entirely 96 | assert.throws(() => decode(Uint8Array.from([131, 1, 2, 0xff]), { allowIndefinite: false }), /indefinite/) 97 | }) 98 | 99 | it('can switch off undefined support', () => { 100 | assert.deepStrictEqual(decode(fromHex('f7')), undefined) 101 | assert.throws(() => decode(fromHex('f7'), { allowUndefined: false }), /undefined/) 102 | assert.deepStrictEqual(decode(fromHex('830102f7')), [1, 2, undefined]) 103 | assert.throws(() => decode(fromHex('830102f7'), { allowUndefined: false }), /undefined/) 104 | }) 105 | 106 | it('can coerce undefined to null', () => { 107 | assert.deepStrictEqual(decode(fromHex('f7'), { coerceUndefinedToNull: false }), undefined) 108 | assert.deepStrictEqual(decode(fromHex('f7'), { coerceUndefinedToNull: true }), null) 109 | assert.deepStrictEqual(decode(fromHex('830102f7'), { coerceUndefinedToNull: false }), [1, 2, undefined]) 110 | assert.deepStrictEqual(decode(fromHex('830102f7'), { coerceUndefinedToNull: true }), [1, 2, null]) 111 | }) 112 | 113 | it('can switch off Infinity support', () => { 114 | assert.deepStrictEqual(decode(fromHex('830102f97c00')), [1, 2, Infinity]) 115 | assert.deepStrictEqual(decode(fromHex('830102f9fc00')), [1, 2, -Infinity]) 116 | assert.throws(() => decode(fromHex('830102f97c00'), { allowInfinity: false }), /Infinity/) 117 | assert.throws(() => decode(fromHex('830102f9fc00'), { allowInfinity: false }), /Infinity/) 118 | for (const fixture of fixtures.filter((f) => f.type.endsWith('Infinity'))) { 119 | assert.throws(() => decode(fromHex(fixture.data), { allowInfinity: false }), /Infinity/) 120 | } 121 | }) 122 | 123 | it('can switch off NaN support', () => { 124 | assert.deepStrictEqual(decode(fromHex('830102f97e00')), [1, 2, NaN]) 125 | assert.throws(() => decode(fromHex('830102f97e00'), { allowNaN: false }), /NaN/) 126 | for (const fixture of fixtures.filter((f) => f.type === 'NaN')) { 127 | assert.throws(() => decode(fromHex(fixture.data), { allowNaN: false }), /NaN/) 128 | } 129 | }) 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /lib/0uint.js: -------------------------------------------------------------------------------- 1 | /* globals BigInt */ 2 | 3 | import { Token, Type } from './token.js' 4 | import { decodeErrPrefix, assertEnoughData } from './common.js' 5 | 6 | export const uintBoundaries = [24, 256, 65536, 4294967296, BigInt('18446744073709551616')] 7 | 8 | /** 9 | * @typedef {import('./bl.js').Bl} Bl 10 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 11 | */ 12 | 13 | /** 14 | * @param {Uint8Array} data 15 | * @param {number} offset 16 | * @param {DecodeOptions} options 17 | * @returns {number} 18 | */ 19 | export function readUint8 (data, offset, options) { 20 | assertEnoughData(data, offset, 1) 21 | const value = data[offset] 22 | if (options.strict === true && value < uintBoundaries[0]) { 23 | throw new Error(`${decodeErrPrefix} integer encoded in more bytes than necessary (strict decode)`) 24 | } 25 | return value 26 | } 27 | 28 | /** 29 | * @param {Uint8Array} data 30 | * @param {number} offset 31 | * @param {DecodeOptions} options 32 | * @returns {number} 33 | */ 34 | export function readUint16 (data, offset, options) { 35 | assertEnoughData(data, offset, 2) 36 | const value = (data[offset] << 8) | data[offset + 1] 37 | if (options.strict === true && value < uintBoundaries[1]) { 38 | throw new Error(`${decodeErrPrefix} integer encoded in more bytes than necessary (strict decode)`) 39 | } 40 | return value 41 | } 42 | 43 | /** 44 | * @param {Uint8Array} data 45 | * @param {number} offset 46 | * @param {DecodeOptions} options 47 | * @returns {number} 48 | */ 49 | export function readUint32 (data, offset, options) { 50 | assertEnoughData(data, offset, 4) 51 | const value = (data[offset] * 16777216 /* 2 ** 24 */) + (data[offset + 1] << 16) + (data[offset + 2] << 8) + data[offset + 3] 52 | if (options.strict === true && value < uintBoundaries[2]) { 53 | throw new Error(`${decodeErrPrefix} integer encoded in more bytes than necessary (strict decode)`) 54 | } 55 | return value 56 | } 57 | 58 | /** 59 | * @param {Uint8Array} data 60 | * @param {number} offset 61 | * @param {DecodeOptions} options 62 | * @returns {number|bigint} 63 | */ 64 | export function readUint64 (data, offset, options) { 65 | // assume BigInt, convert back to Number if within safe range 66 | assertEnoughData(data, offset, 8) 67 | const hi = (data[offset] * 16777216 /* 2 ** 24 */) + (data[offset + 1] << 16) + (data[offset + 2] << 8) + data[offset + 3] 68 | const lo = (data[offset + 4] * 16777216 /* 2 ** 24 */) + (data[offset + 5] << 16) + (data[offset + 6] << 8) + data[offset + 7] 69 | const value = (BigInt(hi) << BigInt(32)) + BigInt(lo) 70 | if (options.strict === true && value < uintBoundaries[3]) { 71 | throw new Error(`${decodeErrPrefix} integer encoded in more bytes than necessary (strict decode)`) 72 | } 73 | if (value <= Number.MAX_SAFE_INTEGER) { 74 | return Number(value) 75 | } 76 | if (options.allowBigInt === true) { 77 | return value 78 | } 79 | throw new Error(`${decodeErrPrefix} integers outside of the safe integer range are not supported`) 80 | } 81 | 82 | /* not required thanks to quick[] list 83 | const oneByteTokens = new Array(24).fill(0).map((v, i) => new Token(Type.uint, i, 1)) 84 | export function decodeUintCompact (data, pos, minor, options) { 85 | return oneByteTokens[minor] 86 | } 87 | */ 88 | 89 | /** 90 | * @param {Uint8Array} data 91 | * @param {number} pos 92 | * @param {number} _minor 93 | * @param {DecodeOptions} options 94 | * @returns {Token} 95 | */ 96 | export function decodeUint8 (data, pos, _minor, options) { 97 | return new Token(Type.uint, readUint8(data, pos + 1, options), 2) 98 | } 99 | 100 | /** 101 | * @param {Uint8Array} data 102 | * @param {number} pos 103 | * @param {number} _minor 104 | * @param {DecodeOptions} options 105 | * @returns {Token} 106 | */ 107 | export function decodeUint16 (data, pos, _minor, options) { 108 | return new Token(Type.uint, readUint16(data, pos + 1, options), 3) 109 | } 110 | 111 | /** 112 | * @param {Uint8Array} data 113 | * @param {number} pos 114 | * @param {number} _minor 115 | * @param {DecodeOptions} options 116 | * @returns {Token} 117 | */ 118 | export function decodeUint32 (data, pos, _minor, options) { 119 | return new Token(Type.uint, readUint32(data, pos + 1, options), 5) 120 | } 121 | 122 | /** 123 | * @param {Uint8Array} data 124 | * @param {number} pos 125 | * @param {number} _minor 126 | * @param {DecodeOptions} options 127 | * @returns {Token} 128 | */ 129 | export function decodeUint64 (data, pos, _minor, options) { 130 | return new Token(Type.uint, readUint64(data, pos + 1, options), 9) 131 | } 132 | 133 | /** 134 | * @param {Bl} buf 135 | * @param {Token} token 136 | */ 137 | export function encodeUint (buf, token) { 138 | return encodeUintValue(buf, 0, token.value) 139 | } 140 | 141 | /** 142 | * @param {Bl} buf 143 | * @param {number} major 144 | * @param {number|bigint} uint 145 | */ 146 | export function encodeUintValue (buf, major, uint) { 147 | if (uint < uintBoundaries[0]) { 148 | const nuint = Number(uint) 149 | // pack into one byte, minor=0, additional=value 150 | buf.push([major | nuint]) 151 | } else if (uint < uintBoundaries[1]) { 152 | const nuint = Number(uint) 153 | // pack into two byte, minor=0, additional=24 154 | buf.push([major | 24, nuint]) 155 | } else if (uint < uintBoundaries[2]) { 156 | const nuint = Number(uint) 157 | // pack into three byte, minor=0, additional=25 158 | buf.push([major | 25, nuint >>> 8, nuint & 0xff]) 159 | } else if (uint < uintBoundaries[3]) { 160 | const nuint = Number(uint) 161 | // pack into five byte, minor=0, additional=26 162 | buf.push([major | 26, (nuint >>> 24) & 0xff, (nuint >>> 16) & 0xff, (nuint >>> 8) & 0xff, nuint & 0xff]) 163 | } else { 164 | const buint = BigInt(uint) 165 | if (buint < uintBoundaries[4]) { 166 | // pack into nine byte, minor=0, additional=27 167 | const set = [major | 27, 0, 0, 0, 0, 0, 0, 0] 168 | // simulate bitwise above 32 bits 169 | let lo = Number(buint & BigInt(0xffffffff)) 170 | let hi = Number(buint >> BigInt(32) & BigInt(0xffffffff)) 171 | set[8] = lo & 0xff 172 | lo = lo >> 8 173 | set[7] = lo & 0xff 174 | lo = lo >> 8 175 | set[6] = lo & 0xff 176 | lo = lo >> 8 177 | set[5] = lo & 0xff 178 | set[4] = hi & 0xff 179 | hi = hi >> 8 180 | set[3] = hi & 0xff 181 | hi = hi >> 8 182 | set[2] = hi & 0xff 183 | hi = hi >> 8 184 | set[1] = hi & 0xff 185 | buf.push(set) 186 | } else { 187 | throw new Error(`${decodeErrPrefix} encountered BigInt larger than allowable range`) 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * @param {Token} token 194 | * @returns {number} 195 | */ 196 | encodeUint.encodedSize = function encodedSize (token) { 197 | return encodeUintValue.encodedSize(token.value) 198 | } 199 | 200 | /** 201 | * @param {number} uint 202 | * @returns {number} 203 | */ 204 | encodeUintValue.encodedSize = function encodedSize (uint) { 205 | if (uint < uintBoundaries[0]) { 206 | return 1 207 | } 208 | if (uint < uintBoundaries[1]) { 209 | return 2 210 | } 211 | if (uint < uintBoundaries[2]) { 212 | return 3 213 | } 214 | if (uint < uintBoundaries[3]) { 215 | return 5 216 | } 217 | return 9 218 | } 219 | 220 | /** 221 | * @param {Token} tok1 222 | * @param {Token} tok2 223 | * @returns {number} 224 | */ 225 | encodeUint.compareTokens = function compareTokens (tok1, tok2) { 226 | return tok1.value < tok2.value ? -1 : tok1.value > tok2.value ? 1 : /* c8 ignore next */ 0 227 | } 228 | -------------------------------------------------------------------------------- /lib/jump.js: -------------------------------------------------------------------------------- 1 | import { Token, Type } from './token.js' 2 | import * as uint from './0uint.js' 3 | import * as negint from './1negint.js' 4 | import * as bytes from './2bytes.js' 5 | import * as string from './3string.js' 6 | import * as array from './4array.js' 7 | import * as map from './5map.js' 8 | import * as tag from './6tag.js' 9 | import * as float from './7float.js' 10 | import { decodeErrPrefix } from './common.js' 11 | import { fromArray } from './byte-utils.js' 12 | 13 | /** 14 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 15 | */ 16 | 17 | /** 18 | * @param {Uint8Array} data 19 | * @param {number} pos 20 | * @param {number} minor 21 | */ 22 | function invalidMinor (data, pos, minor) { 23 | throw new Error(`${decodeErrPrefix} encountered invalid minor (${minor}) for major ${data[pos] >>> 5}`) 24 | } 25 | 26 | /** 27 | * @param {string} msg 28 | * @returns {()=>any} 29 | */ 30 | function errorer (msg) { 31 | return () => { throw new Error(`${decodeErrPrefix} ${msg}`) } 32 | } 33 | 34 | /** @type {((data:Uint8Array, pos:number, minor:number, options?:DecodeOptions) => any)[]} */ 35 | export const jump = [] 36 | 37 | // unsigned integer, 0x00..0x17 (0..23) 38 | for (let i = 0; i <= 0x17; i++) { 39 | jump[i] = invalidMinor // uint.decodeUintCompact, handled by quick[] 40 | } 41 | jump[0x18] = uint.decodeUint8 // unsigned integer, one-byte uint8_t follows 42 | jump[0x19] = uint.decodeUint16 // unsigned integer, two-byte uint16_t follows 43 | jump[0x1a] = uint.decodeUint32 // unsigned integer, four-byte uint32_t follows 44 | jump[0x1b] = uint.decodeUint64 // unsigned integer, eight-byte uint64_t follows 45 | jump[0x1c] = invalidMinor 46 | jump[0x1d] = invalidMinor 47 | jump[0x1e] = invalidMinor 48 | jump[0x1f] = invalidMinor 49 | // negative integer, -1-0x00..-1-0x17 (-1..-24) 50 | for (let i = 0x20; i <= 0x37; i++) { 51 | jump[i] = invalidMinor // negintDecode, handled by quick[] 52 | } 53 | jump[0x38] = negint.decodeNegint8 // negative integer, -1-n one-byte uint8_t for n follows 54 | jump[0x39] = negint.decodeNegint16 // negative integer, -1-n two-byte uint16_t for n follows 55 | jump[0x3a] = negint.decodeNegint32 // negative integer, -1-n four-byte uint32_t for follows 56 | jump[0x3b] = negint.decodeNegint64 // negative integer, -1-n eight-byte uint64_t for follows 57 | jump[0x3c] = invalidMinor 58 | jump[0x3d] = invalidMinor 59 | jump[0x3e] = invalidMinor 60 | jump[0x3f] = invalidMinor 61 | // byte string, 0x00..0x17 bytes follow 62 | for (let i = 0x40; i <= 0x57; i++) { 63 | jump[i] = bytes.decodeBytesCompact 64 | } 65 | jump[0x58] = bytes.decodeBytes8 // byte string, one-byte uint8_t for n, and then n bytes follow 66 | jump[0x59] = bytes.decodeBytes16 // byte string, two-byte uint16_t for n, and then n bytes follow 67 | jump[0x5a] = bytes.decodeBytes32 // byte string, four-byte uint32_t for n, and then n bytes follow 68 | jump[0x5b] = bytes.decodeBytes64 // byte string, eight-byte uint64_t for n, and then n bytes follow 69 | jump[0x5c] = invalidMinor 70 | jump[0x5d] = invalidMinor 71 | jump[0x5e] = invalidMinor 72 | jump[0x5f] = errorer('indefinite length bytes/strings are not supported') // byte string, byte strings follow, terminated by "break" 73 | // UTF-8 string 0x00..0x17 bytes follow 74 | for (let i = 0x60; i <= 0x77; i++) { 75 | jump[i] = string.decodeStringCompact 76 | } 77 | jump[0x78] = string.decodeString8 // UTF-8 string, one-byte uint8_t for n, and then n bytes follow 78 | jump[0x79] = string.decodeString16 // UTF-8 string, two-byte uint16_t for n, and then n bytes follow 79 | jump[0x7a] = string.decodeString32 // UTF-8 string, four-byte uint32_t for n, and then n bytes follow 80 | jump[0x7b] = string.decodeString64 // UTF-8 string, eight-byte uint64_t for n, and then n bytes follow 81 | jump[0x7c] = invalidMinor 82 | jump[0x7d] = invalidMinor 83 | jump[0x7e] = invalidMinor 84 | jump[0x7f] = errorer('indefinite length bytes/strings are not supported') // UTF-8 strings follow, terminated by "break" 85 | // array, 0x00..0x17 data items follow 86 | for (let i = 0x80; i <= 0x97; i++) { 87 | jump[i] = array.decodeArrayCompact 88 | } 89 | jump[0x98] = array.decodeArray8 // array, one-byte uint8_t for n, and then n data items follow 90 | jump[0x99] = array.decodeArray16 // array, two-byte uint16_t for n, and then n data items follow 91 | jump[0x9a] = array.decodeArray32 // array, four-byte uint32_t for n, and then n data items follow 92 | jump[0x9b] = array.decodeArray64 // array, eight-byte uint64_t for n, and then n data items follow 93 | jump[0x9c] = invalidMinor 94 | jump[0x9d] = invalidMinor 95 | jump[0x9e] = invalidMinor 96 | jump[0x9f] = array.decodeArrayIndefinite // array, data items follow, terminated by "break" 97 | // map, 0x00..0x17 pairs of data items follow 98 | for (let i = 0xa0; i <= 0xb7; i++) { 99 | jump[i] = map.decodeMapCompact 100 | } 101 | jump[0xb8] = map.decodeMap8 // map, one-byte uint8_t for n, and then n pairs of data items follow 102 | jump[0xb9] = map.decodeMap16 // map, two-byte uint16_t for n, and then n pairs of data items follow 103 | jump[0xba] = map.decodeMap32 // map, four-byte uint32_t for n, and then n pairs of data items follow 104 | jump[0xbb] = map.decodeMap64 // map, eight-byte uint64_t for n, and then n pairs of data items follow 105 | jump[0xbc] = invalidMinor 106 | jump[0xbd] = invalidMinor 107 | jump[0xbe] = invalidMinor 108 | jump[0xbf] = map.decodeMapIndefinite // map, pairs of data items follow, terminated by "break" 109 | // tags 110 | for (let i = 0xc0; i <= 0xd7; i++) { 111 | jump[i] = tag.decodeTagCompact 112 | } 113 | jump[0xd8] = tag.decodeTag8 114 | jump[0xd9] = tag.decodeTag16 115 | jump[0xda] = tag.decodeTag32 116 | jump[0xdb] = tag.decodeTag64 117 | jump[0xdc] = invalidMinor 118 | jump[0xdd] = invalidMinor 119 | jump[0xde] = invalidMinor 120 | jump[0xdf] = invalidMinor 121 | // 0xe0..0xf3 simple values, unsupported 122 | for (let i = 0xe0; i <= 0xf3; i++) { 123 | jump[i] = errorer('simple values are not supported') 124 | } 125 | jump[0xf4] = invalidMinor // false, handled by quick[] 126 | jump[0xf5] = invalidMinor // true, handled by quick[] 127 | jump[0xf6] = invalidMinor // null, handled by quick[] 128 | jump[0xf7] = float.decodeUndefined // undefined 129 | jump[0xf8] = errorer('simple values are not supported') // simple value, one byte follows, unsupported 130 | jump[0xf9] = float.decodeFloat16 // half-precision float (two-byte IEEE 754) 131 | jump[0xfa] = float.decodeFloat32 // single-precision float (four-byte IEEE 754) 132 | jump[0xfb] = float.decodeFloat64 // double-precision float (eight-byte IEEE 754) 133 | jump[0xfc] = invalidMinor 134 | jump[0xfd] = invalidMinor 135 | jump[0xfe] = invalidMinor 136 | jump[0xff] = float.decodeBreak // "break" stop code 137 | 138 | /** @type {Token[]} */ 139 | export const quick = [] 140 | // ints <24 141 | for (let i = 0; i < 24; i++) { 142 | quick[i] = new Token(Type.uint, i, 1) 143 | } 144 | // negints >= -24 145 | for (let i = -1; i >= -24; i--) { 146 | quick[31 - i] = new Token(Type.negint, i, 1) 147 | } 148 | // empty bytes 149 | quick[0x40] = new Token(Type.bytes, new Uint8Array(0), 1) 150 | // empty string 151 | quick[0x60] = new Token(Type.string, '', 1) 152 | // empty list 153 | quick[0x80] = new Token(Type.array, 0, 1) 154 | // empty map 155 | quick[0xa0] = new Token(Type.map, 0, 1) 156 | // false 157 | quick[0xf4] = new Token(Type.false, false, 1) 158 | // true 159 | quick[0xf5] = new Token(Type.true, true, 1) 160 | // null 161 | quick[0xf6] = new Token(Type.null, null, 1) 162 | 163 | /** 164 | * @param {Token} token 165 | * @returns {Uint8Array|undefined} 166 | */ 167 | export function quickEncodeToken (token) { 168 | switch (token.type) { 169 | case Type.false: 170 | return fromArray([0xf4]) 171 | case Type.true: 172 | return fromArray([0xf5]) 173 | case Type.null: 174 | return fromArray([0xf6]) 175 | case Type.bytes: 176 | if (!token.value.length) { 177 | return fromArray([0x40]) 178 | } 179 | return 180 | case Type.string: 181 | if (token.value === '') { 182 | return fromArray([0x60]) 183 | } 184 | return 185 | case Type.array: 186 | if (token.value === 0) { 187 | return fromArray([0x80]) 188 | } 189 | /* c8 ignore next 2 */ 190 | // shouldn't be possible if this were called when there was only one token 191 | return 192 | case Type.map: 193 | if (token.value === 0) { 194 | return fromArray([0xa0]) 195 | } 196 | /* c8 ignore next 2 */ 197 | // shouldn't be possible if this were called when there was only one token 198 | return 199 | case Type.uint: 200 | if (token.value < 24) { 201 | return fromArray([Number(token.value)]) 202 | } 203 | return 204 | case Type.negint: 205 | if (token.value >= -24) { 206 | return fromArray([31 - Number(token.value)]) 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /lib/json/encode.js: -------------------------------------------------------------------------------- 1 | import { Type } from '../token.js' 2 | import { encodeCustom } from '../encode.js' 3 | import { encodeErrPrefix } from '../common.js' 4 | import { asU8A, fromString } from '../byte-utils.js' 5 | 6 | /** 7 | * @typedef {import('../../interface').EncodeOptions} EncodeOptions 8 | * @typedef {import('../token').Token} Token 9 | * @typedef {import('../bl').Bl} Bl 10 | */ 11 | 12 | class JSONEncoder extends Array { 13 | constructor () { 14 | super() 15 | /** @type {{type:Type,elements:number}[]} */ 16 | this.inRecursive = [] 17 | } 18 | 19 | /** 20 | * @param {Bl} buf 21 | */ 22 | prefix (buf) { 23 | const recurs = this.inRecursive[this.inRecursive.length - 1] 24 | if (recurs) { 25 | if (recurs.type === Type.array) { 26 | recurs.elements++ 27 | if (recurs.elements !== 1) { // >first 28 | buf.push([44]) // ',' 29 | } 30 | } 31 | if (recurs.type === Type.map) { 32 | recurs.elements++ 33 | if (recurs.elements !== 1) { // >first 34 | if (recurs.elements % 2 === 1) { // key 35 | buf.push([44]) // ',' 36 | } else { 37 | buf.push([58]) // ':' 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * @param {Bl} buf 46 | * @param {Token} token 47 | */ 48 | [Type.uint.major] (buf, token) { 49 | this.prefix(buf) 50 | const is = String(token.value) 51 | const isa = [] 52 | for (let i = 0; i < is.length; i++) { 53 | isa[i] = is.charCodeAt(i) 54 | } 55 | buf.push(isa) 56 | } 57 | 58 | /** 59 | * @param {Bl} buf 60 | * @param {Token} token 61 | */ 62 | [Type.negint.major] (buf, token) { 63 | // @ts-ignore hack 64 | this[Type.uint.major](buf, token) 65 | } 66 | 67 | /** 68 | * @param {Bl} _buf 69 | * @param {Token} _token 70 | */ 71 | [Type.bytes.major] (_buf, _token) { 72 | throw new Error(`${encodeErrPrefix} unsupported type: Uint8Array`) 73 | } 74 | 75 | /** 76 | * @param {Bl} buf 77 | * @param {Token} token 78 | */ 79 | [Type.string.major] (buf, token) { 80 | this.prefix(buf) 81 | // buf.push(34) // '"' 82 | // encodeUtf8(token.value, byts) 83 | // buf.push(34) // '"' 84 | const byts = fromString(JSON.stringify(token.value)) 85 | buf.push(byts.length > 32 ? asU8A(byts) : byts) 86 | } 87 | 88 | /** 89 | * @param {Bl} buf 90 | * @param {Token} _token 91 | */ 92 | [Type.array.major] (buf, _token) { 93 | this.prefix(buf) 94 | this.inRecursive.push({ type: Type.array, elements: 0 }) 95 | buf.push([91]) // '[' 96 | } 97 | 98 | /** 99 | * @param {Bl} buf 100 | * @param {Token} _token 101 | */ 102 | [Type.map.major] (buf, _token) { 103 | this.prefix(buf) 104 | this.inRecursive.push({ type: Type.map, elements: 0 }) 105 | buf.push([123]) // '{' 106 | } 107 | 108 | /** 109 | * @param {Bl} _buf 110 | * @param {Token} _token 111 | */ 112 | [Type.tag.major] (_buf, _token) {} 113 | 114 | /** 115 | * @param {Bl} buf 116 | * @param {Token} token 117 | */ 118 | [Type.float.major] (buf, token) { 119 | if (token.type.name === 'break') { 120 | const recurs = this.inRecursive.pop() 121 | if (recurs) { 122 | if (recurs.type === Type.array) { 123 | buf.push([93]) // ']' 124 | } else if (recurs.type === Type.map) { 125 | buf.push([125]) // '}' 126 | /* c8 ignore next 3 */ 127 | } else { 128 | throw new Error('Unexpected recursive type; this should not happen!') 129 | } 130 | return 131 | } 132 | /* c8 ignore next 2 */ 133 | throw new Error('Unexpected break; this should not happen!') 134 | } 135 | if (token.value === undefined) { 136 | throw new Error(`${encodeErrPrefix} unsupported type: undefined`) 137 | } 138 | 139 | this.prefix(buf) 140 | if (token.type.name === 'true') { 141 | buf.push([116, 114, 117, 101]) // 'true' 142 | return 143 | } else if (token.type.name === 'false') { 144 | buf.push([102, 97, 108, 115, 101]) // 'false' 145 | return 146 | } else if (token.type.name === 'null') { 147 | buf.push([110, 117, 108, 108]) // 'null' 148 | return 149 | } 150 | 151 | // number 152 | const is = String(token.value) 153 | const isa = [] 154 | let dp = false 155 | for (let i = 0; i < is.length; i++) { 156 | isa[i] = is.charCodeAt(i) 157 | if (!dp && (isa[i] === 46 || isa[i] === 101 || isa[i] === 69)) { // '[.eE]' 158 | dp = true 159 | } 160 | } 161 | if (!dp) { // need a decimal point for floats 162 | isa.push(46) // '.' 163 | isa.push(48) // '0' 164 | } 165 | buf.push(isa) 166 | } 167 | } 168 | 169 | // The below code is mostly taken and modified from https://github.com/feross/buffer 170 | // Licensed MIT. Copyright (c) Feross Aboukhadijeh 171 | // function encodeUtf8 (string, byts) { 172 | // let codePoint 173 | // const length = string.length 174 | // let leadSurrogate = null 175 | 176 | // for (let i = 0; i < length; ++i) { 177 | // codePoint = string.charCodeAt(i) 178 | 179 | // // is surrogate component 180 | // if (codePoint > 0xd7ff && codePoint < 0xe000) { 181 | // // last char was a lead 182 | // if (!leadSurrogate) { 183 | // // no lead yet 184 | // /* c8 ignore next 9 */ 185 | // if (codePoint > 0xdbff) { 186 | // // unexpected trail 187 | // byts.push(0xef, 0xbf, 0xbd) 188 | // continue 189 | // } else if (i + 1 === length) { 190 | // // unpaired lead 191 | // byts.push(0xef, 0xbf, 0xbd) 192 | // continue 193 | // } 194 | 195 | // // valid lead 196 | // leadSurrogate = codePoint 197 | 198 | // continue 199 | // } 200 | 201 | // // 2 leads in a row 202 | // /* c8 ignore next 5 */ 203 | // if (codePoint < 0xdc00) { 204 | // byts.push(0xef, 0xbf, 0xbd) 205 | // leadSurrogate = codePoint 206 | // continue 207 | // } 208 | 209 | // // valid surrogate pair 210 | // codePoint = (leadSurrogate - 0xd800 << 10 | codePoint - 0xdc00) + 0x10000 211 | // /* c8 ignore next 4 */ 212 | // } else if (leadSurrogate) { 213 | // // valid bmp char, but last char was a lead 214 | // byts.push(0xef, 0xbf, 0xbd) 215 | // } 216 | 217 | // leadSurrogate = null 218 | 219 | // // encode utf8 220 | // if (codePoint < 0x80) { 221 | // // special JSON escapes 222 | // switch (codePoint) { 223 | // case 8: // '\b' 224 | // byts.push(92, 98) // '\\b' 225 | // continue 226 | // case 9: // '\t' 227 | // byts.push(92, 116) // '\\t' 228 | // continue 229 | // case 10: // '\n' 230 | // byts.push(92, 110) // '\\n' 231 | // continue 232 | // case 12: // '\f' 233 | // byts.push(92, 102) // '\\f' 234 | // continue 235 | // case 13: // '\r' 236 | // byts.push(92, 114) // '\\r' 237 | // continue 238 | // case 34: // '"' 239 | // byts.push(92, 34) // '\\"' 240 | // continue 241 | // case 92: // '\\' 242 | // byts.push(92, 92) // '\\\\' 243 | // continue 244 | // } 245 | 246 | // byts.push(codePoint) 247 | // } else if (codePoint < 0x800) { 248 | // /* c8 ignore next 1 */ 249 | // byts.push( 250 | // codePoint >> 0x6 | 0xc0, 251 | // codePoint & 0x3f | 0x80 252 | // ) 253 | // } else if (codePoint < 0x10000) { 254 | // /* c8 ignore next 1 */ 255 | // byts.push( 256 | // codePoint >> 0xc | 0xe0, 257 | // codePoint >> 0x6 & 0x3f | 0x80, 258 | // codePoint & 0x3f | 0x80 259 | // ) 260 | // /* c8 ignore next 9 */ 261 | // } else if (codePoint < 0x110000) { 262 | // byts.push( 263 | // codePoint >> 0x12 | 0xf0, 264 | // codePoint >> 0xc & 0x3f | 0x80, 265 | // codePoint >> 0x6 & 0x3f | 0x80, 266 | // codePoint & 0x3f | 0x80 267 | // ) 268 | // } else { 269 | // /* c8 ignore next 2 */ 270 | // throw new Error('Invalid code point') 271 | // } 272 | // } 273 | // } 274 | 275 | /** 276 | * @param {(Token|Token[])[]} e1 277 | * @param {(Token|Token[])[]} e2 278 | * @returns {number} 279 | */ 280 | function mapSorter (e1, e2) { 281 | if (Array.isArray(e1[0]) || Array.isArray(e2[0])) { 282 | throw new Error(`${encodeErrPrefix} complex map keys are not supported`) 283 | } 284 | const keyToken1 = e1[0] 285 | const keyToken2 = e2[0] 286 | if (keyToken1.type !== Type.string || keyToken2.type !== Type.string) { 287 | throw new Error(`${encodeErrPrefix} non-string map keys are not supported`) 288 | } 289 | if (keyToken1 < keyToken2) { 290 | return -1 291 | } 292 | if (keyToken1 > keyToken2) { 293 | return 1 294 | } 295 | /* c8 ignore next 1 */ 296 | throw new Error(`${encodeErrPrefix} unexpected duplicate map keys, this is not supported`) 297 | } 298 | 299 | const defaultEncodeOptions = { addBreakTokens: true, mapSorter } 300 | 301 | /** 302 | * @param {any} data 303 | * @param {EncodeOptions} [options] 304 | * @returns {Uint8Array} 305 | */ 306 | function encode (data, options) { 307 | options = Object.assign({}, defaultEncodeOptions, options) 308 | // @ts-ignore TokenTypeEncoder[] requires compareTokens() on each encoder, we don't use them here 309 | return encodeCustom(data, new JSONEncoder(), options) 310 | } 311 | 312 | export { encode } 313 | -------------------------------------------------------------------------------- /lib/7float.js: -------------------------------------------------------------------------------- 1 | // TODO: shift some of the bytes logic to bytes-utils so we can use Buffer 2 | // where possible 3 | 4 | import { Token, Type } from './token.js' 5 | import { decodeErrPrefix } from './common.js' 6 | import { encodeUint } from './0uint.js' 7 | 8 | /** 9 | * @typedef {import('./bl.js').Bl} Bl 10 | * @typedef {import('../interface').DecodeOptions} DecodeOptions 11 | * @typedef {import('../interface').EncodeOptions} EncodeOptions 12 | */ 13 | 14 | const MINOR_FALSE = 20 15 | const MINOR_TRUE = 21 16 | const MINOR_NULL = 22 17 | const MINOR_UNDEFINED = 23 18 | 19 | /** 20 | * @param {Uint8Array} _data 21 | * @param {number} _pos 22 | * @param {number} _minor 23 | * @param {DecodeOptions} options 24 | * @returns {Token} 25 | */ 26 | export function decodeUndefined (_data, _pos, _minor, options) { 27 | if (options.allowUndefined === false) { 28 | throw new Error(`${decodeErrPrefix} undefined values are not supported`) 29 | } else if (options.coerceUndefinedToNull === true) { 30 | return new Token(Type.null, null, 1) 31 | } 32 | return new Token(Type.undefined, undefined, 1) 33 | } 34 | 35 | /** 36 | * @param {Uint8Array} _data 37 | * @param {number} _pos 38 | * @param {number} _minor 39 | * @param {DecodeOptions} options 40 | * @returns {Token} 41 | */ 42 | export function decodeBreak (_data, _pos, _minor, options) { 43 | if (options.allowIndefinite === false) { 44 | throw new Error(`${decodeErrPrefix} indefinite length items not allowed`) 45 | } 46 | return new Token(Type.break, undefined, 1) 47 | } 48 | 49 | /** 50 | * @param {number} value 51 | * @param {number} bytes 52 | * @param {DecodeOptions} options 53 | * @returns {Token} 54 | */ 55 | function createToken (value, bytes, options) { 56 | if (options) { 57 | if (options.allowNaN === false && Number.isNaN(value)) { 58 | throw new Error(`${decodeErrPrefix} NaN values are not supported`) 59 | } 60 | if (options.allowInfinity === false && (value === Infinity || value === -Infinity)) { 61 | throw new Error(`${decodeErrPrefix} Infinity values are not supported`) 62 | } 63 | } 64 | return new Token(Type.float, value, bytes) 65 | } 66 | 67 | /** 68 | * @param {Uint8Array} data 69 | * @param {number} pos 70 | * @param {number} _minor 71 | * @param {DecodeOptions} options 72 | * @returns {Token} 73 | */ 74 | export function decodeFloat16 (data, pos, _minor, options) { 75 | return createToken(readFloat16(data, pos + 1), 3, options) 76 | } 77 | 78 | /** 79 | * @param {Uint8Array} data 80 | * @param {number} pos 81 | * @param {number} _minor 82 | * @param {DecodeOptions} options 83 | * @returns {Token} 84 | */ 85 | export function decodeFloat32 (data, pos, _minor, options) { 86 | return createToken(readFloat32(data, pos + 1), 5, options) 87 | } 88 | 89 | /** 90 | * @param {Uint8Array} data 91 | * @param {number} pos 92 | * @param {number} _minor 93 | * @param {DecodeOptions} options 94 | * @returns {Token} 95 | */ 96 | export function decodeFloat64 (data, pos, _minor, options) { 97 | return createToken(readFloat64(data, pos + 1), 9, options) 98 | } 99 | 100 | /** 101 | * @param {Bl} buf 102 | * @param {Token} token 103 | * @param {EncodeOptions} options 104 | */ 105 | export function encodeFloat (buf, token, options) { 106 | const float = token.value 107 | 108 | if (float === false) { 109 | buf.push([Type.float.majorEncoded | MINOR_FALSE]) 110 | } else if (float === true) { 111 | buf.push([Type.float.majorEncoded | MINOR_TRUE]) 112 | } else if (float === null) { 113 | buf.push([Type.float.majorEncoded | MINOR_NULL]) 114 | } else if (float === undefined) { 115 | buf.push([Type.float.majorEncoded | MINOR_UNDEFINED]) 116 | } else { 117 | let decoded 118 | let success = false 119 | if (!options || options.float64 !== true) { 120 | encodeFloat16(float) 121 | decoded = readFloat16(ui8a, 1) 122 | if (float === decoded || Number.isNaN(float)) { 123 | ui8a[0] = 0xf9 124 | buf.push(ui8a.slice(0, 3)) 125 | success = true 126 | } else { 127 | encodeFloat32(float) 128 | decoded = readFloat32(ui8a, 1) 129 | if (float === decoded) { 130 | ui8a[0] = 0xfa 131 | buf.push(ui8a.slice(0, 5)) 132 | success = true 133 | } 134 | } 135 | } 136 | if (!success) { 137 | encodeFloat64(float) 138 | decoded = readFloat64(ui8a, 1) 139 | ui8a[0] = 0xfb 140 | buf.push(ui8a.slice(0, 9)) 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * @param {Token} token 147 | * @param {EncodeOptions} options 148 | * @returns {number} 149 | */ 150 | encodeFloat.encodedSize = function encodedSize (token, options) { 151 | const float = token.value 152 | 153 | if (float === false || float === true || float === null || float === undefined) { 154 | return 1 155 | } 156 | 157 | if (!options || options.float64 !== true) { 158 | encodeFloat16(float) 159 | let decoded = readFloat16(ui8a, 1) 160 | if (float === decoded || Number.isNaN(float)) { 161 | return 3 162 | } 163 | encodeFloat32(float) 164 | decoded = readFloat32(ui8a, 1) 165 | if (float === decoded) { 166 | return 5 167 | } 168 | } 169 | return 9 170 | } 171 | 172 | const buffer = new ArrayBuffer(9) 173 | const dataView = new DataView(buffer, 1) 174 | const ui8a = new Uint8Array(buffer, 0) 175 | 176 | /** 177 | * @param {number} inp 178 | */ 179 | function encodeFloat16 (inp) { 180 | if (inp === Infinity) { 181 | dataView.setUint16(0, 0x7c00, false) 182 | } else if (inp === -Infinity) { 183 | dataView.setUint16(0, 0xfc00, false) 184 | } else if (Number.isNaN(inp)) { 185 | dataView.setUint16(0, 0x7e00, false) 186 | } else { 187 | dataView.setFloat32(0, inp) 188 | const valu32 = dataView.getUint32(0) 189 | const exponent = (valu32 & 0x7f800000) >> 23 190 | const mantissa = valu32 & 0x7fffff 191 | 192 | /* c8 ignore next 6 */ 193 | if (exponent === 0xff) { 194 | // too big, Infinity, but this should be hard (impossible?) to trigger 195 | dataView.setUint16(0, 0x7c00, false) 196 | } else if (exponent === 0x00) { 197 | // 0.0, -0.0 and subnormals, shouldn't be possible to get here because 0.0 should be counted as an int 198 | dataView.setUint16(0, ((inp & 0x80000000) >> 16) | (mantissa >> 13), false) 199 | } else { // standard numbers 200 | // chunks of logic here borrowed from https://github.com/PJK/libcbor/blob/c78f437182533e3efa8d963ff4b945bb635c2284/src/cbor/encoding.c#L127 201 | const logicalExponent = exponent - 127 202 | // Now we know that 2^exponent <= 0 logically 203 | /* c8 ignore next 6 */ 204 | if (logicalExponent < -24) { 205 | /* No unambiguous representation exists, this float is not a half float 206 | and is too small to be represented using a half, round off to zero. 207 | Consistent with the reference implementation. */ 208 | // should be difficult (impossible?) to get here in JS 209 | dataView.setUint16(0, 0) 210 | } else if (logicalExponent < -14) { 211 | /* Offset the remaining decimal places by shifting the significand, the 212 | value is lost. This is an implementation decision that works around the 213 | absence of standard half-float in the language. */ 214 | dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | /* sign bit */ (1 << (24 + logicalExponent)), false) 215 | } else { 216 | dataView.setUint16(0, ((valu32 & 0x80000000) >> 16) | ((logicalExponent + 15) << 10) | (mantissa >> 13), false) 217 | } 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * @param {Uint8Array} ui8a 224 | * @param {number} pos 225 | * @returns {number} 226 | */ 227 | function readFloat16 (ui8a, pos) { 228 | if (ui8a.length - pos < 2) { 229 | throw new Error(`${decodeErrPrefix} not enough data for float16`) 230 | } 231 | 232 | const half = (ui8a[pos] << 8) + ui8a[pos + 1] 233 | if (half === 0x7c00) { 234 | return Infinity 235 | } 236 | if (half === 0xfc00) { 237 | return -Infinity 238 | } 239 | if (half === 0x7e00) { 240 | return NaN 241 | } 242 | const exp = (half >> 10) & 0x1f 243 | const mant = half & 0x3ff 244 | let val 245 | if (exp === 0) { 246 | val = mant * (2 ** -24) 247 | } else if (exp !== 31) { 248 | val = (mant + 1024) * (2 ** (exp - 25)) 249 | /* c8 ignore next 4 */ 250 | } else { 251 | // may not be possible to get here 252 | val = mant === 0 ? Infinity : NaN 253 | } 254 | return (half & 0x8000) ? -val : val 255 | } 256 | 257 | /** 258 | * @param {number} inp 259 | */ 260 | function encodeFloat32 (inp) { 261 | dataView.setFloat32(0, inp, false) 262 | } 263 | 264 | /** 265 | * @param {Uint8Array} ui8a 266 | * @param {number} pos 267 | * @returns {number} 268 | */ 269 | function readFloat32 (ui8a, pos) { 270 | if (ui8a.length - pos < 4) { 271 | throw new Error(`${decodeErrPrefix} not enough data for float32`) 272 | } 273 | const offset = (ui8a.byteOffset || 0) + pos 274 | return new DataView(ui8a.buffer, offset, 4).getFloat32(0, false) 275 | } 276 | 277 | /** 278 | * @param {number} inp 279 | */ 280 | function encodeFloat64 (inp) { 281 | dataView.setFloat64(0, inp, false) 282 | } 283 | 284 | /** 285 | * @param {Uint8Array} ui8a 286 | * @param {number} pos 287 | * @returns {number} 288 | */ 289 | function readFloat64 (ui8a, pos) { 290 | if (ui8a.length - pos < 8) { 291 | throw new Error(`${decodeErrPrefix} not enough data for float64`) 292 | } 293 | const offset = (ui8a.byteOffset || 0) + pos 294 | return new DataView(ui8a.buffer, offset, 8).getFloat64(0, false) 295 | } 296 | 297 | /** 298 | * @param {Token} _tok1 299 | * @param {Token} _tok2 300 | * @returns {number} 301 | */ 302 | encodeFloat.compareTokens = encodeUint.compareTokens 303 | /* 304 | encodeFloat.compareTokens = function compareTokens (_tok1, _tok2) { 305 | return _tok1 306 | throw new Error(`${encodeErrPrefix} cannot use floats as map keys`) 307 | } 308 | */ 309 | --------------------------------------------------------------------------------