cbor-x 0.8.2 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +22 -0
- package/README.md +54 -18
- package/SECURITY.md +11 -0
- package/{unpack.d.ts → decode.d.ts} +12 -11
- package/decode.js +134 -33
- package/dist/index.js +400 -57
- package/dist/index.min.js +83 -53
- package/dist/node.cjs +419 -80
- package/dist/test.js +82 -1
- package/encode.d.ts +6 -0
- package/encode.js +181 -24
- package/index.d.ts +7 -6
- package/index.js +2 -2
- package/iterators.js +85 -0
- package/{node.js → node-index.js} +4 -3
- package/package.json +17 -17
- package/rollup.config.js +2 -2
- package/stream.js +20 -22
- package/tests/benchmark.cjs +48 -8
- package/tests/test-compatibility.cjs +1 -0
- package/tests/test-incomplete.js +41 -0
- package/tests/test-node-iterators.js +72 -0
- package/tests/test-node-stream.js +11 -1
- package/tests/test.js +82 -2
- package/pack.d.ts +0 -9
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "pwa-node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Run cbor-x tests",
|
|
11
|
+
"runtimeArgs": [
|
|
12
|
+
"test"
|
|
13
|
+
],
|
|
14
|
+
"runtimeExecutable": "npm",
|
|
15
|
+
"skipFiles": [
|
|
16
|
+
"<node_internals>/**"
|
|
17
|
+
],
|
|
18
|
+
"console": "internalConsole",
|
|
19
|
+
"outputCapture": "std"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](README.md)
|
|
7
7
|
[](README.md)
|
|
8
8
|
|
|
9
|
-
The cbor-x package is an extremely fast CBOR NodeJS/JavaScript implementation. Currently, it is significantly faster than any other known implementations, faster than Avro (for JS), and generally faster than native V8 JSON.stringify/parse. It implements the CBOR format as specificed in [RFC-8949](https://www.rfc-editor.org/rfc/rfc8949.html), numerous [registered IANA tag extensions](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml) (the `x` in cbor-x), [RFC-8746](https://tools.ietf.org/html/rfc8746) and proposed optional record extension, for defining record structures that makes CBOR even faster and more compact, often over twice as fast as even native JSON functions, several times faster than other JS implementations, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is supported through these tag extensions.
|
|
9
|
+
The cbor-x package is an extremely fast CBOR NodeJS/JavaScript implementation. Currently, it is significantly faster than any other known implementations, faster than Avro (for JS), and generally faster than native V8 JSON.stringify/parse, on NodeJS. It implements the CBOR format as specificed in [RFC-8949](https://www.rfc-editor.org/rfc/rfc8949.html), numerous [registered IANA tag extensions](https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml) (the `x` in cbor-x), [RFC-8746](https://tools.ietf.org/html/rfc8746) and proposed optional [record extension](https://github.com/kriszyp/cbor-records), for defining record structures that makes CBOR even faster and more compact, often over twice as fast as even native JSON functions, several times faster than other JS implementations, and 15-50% more compact. See the performance section for more details. Structured cloning (with support for cyclical references) is supported through these tag extensions.
|
|
10
10
|
|
|
11
11
|
## Basic Usage
|
|
12
12
|
|
|
@@ -48,7 +48,7 @@ receivingStream.on('data', (data) => {
|
|
|
48
48
|
// received data
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
|
-
|
|
51
|
+
The `EncoderStream` and `DecoderStream` instances will have also the record structure extension enabled by default (see below).
|
|
52
52
|
|
|
53
53
|
## Deno Usage
|
|
54
54
|
CBOR modules are standard ESM modules and can be loaded directly from github (https://raw.githubusercontent.com/kriszyp/cbor-x/master/index.js) or downloaded and used directly in Deno. The standard encode and decode functionality is available on Deno, like other platforms.
|
|
@@ -82,10 +82,10 @@ copy.set.has('a') // true
|
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
This option is disabled by default because
|
|
85
|
+
This option is disabled by default because reference checking degrades performance (by about 25-30%). (Note this implementation doesn't serialize every class/type specified in the HTML specification since not all of them make sense for storing across platforms.)
|
|
86
86
|
|
|
87
87
|
## Record / Object Structures
|
|
88
|
-
There is a critical difference between maps (or dictionaries) that hold an arbitrary set of keys and values (JavaScript `Map` is designed for these), and records or object structures that have a well-defined set of fields. Typical JS objects/records may have many instances re(use) the same structure. By using the record extension, this distinction is preserved in CBOR and the encoding can reuse structures and not only provides better type preservation, but yield much more compact encodings and increase decoding performance by 2-3x.
|
|
88
|
+
There is a critical difference between maps (or dictionaries) that hold an arbitrary set of keys and values (JavaScript `Map` is designed for these), and records or object structures that have a well-defined set of fields. Typical JS objects/records may have many instances re(use) the same structure. By using the record extension, this distinction is preserved in CBOR and the encoding can reuse structures and not only provides better type preservation, but yield much more compact encodings and increase decoding performance by 2-3x. Cbor-x automatically generates record definitions that are reused and referenced by objects with the same structure. Records use CBOR's tags to align well CBOR's tag/extension mechanism. There are a number of ways to use this to our advantage. For large object structures with repeating nested objects with similar structures, simply serializing with the record extension can yield significant benefits. To use the record structures extension, we create a new `Encoder` instance. By default a new `Encoder` instance will have the record extension enabled:
|
|
89
89
|
```
|
|
90
90
|
import { Encoder } from 'cbor-x';
|
|
91
91
|
let encoder = new Encoder();
|
|
@@ -96,6 +96,8 @@ Another way to further leverage the benefits of the cbor-x record structures is
|
|
|
96
96
|
|
|
97
97
|
When creating a new `Encoder`, `EncoderStream`, or `DecoderStream` instance, we can enable or disable the record structure extension with the `objectsAsMaps` property. When this is `true`, the record structure extension will be disabled, and all objects will revert to being serialized using MessageMap `map`s, and all `map`s will be deserialized to JS `Object`s as properties (like the standalone `encode` and `decode` functions).
|
|
98
98
|
|
|
99
|
+
Streaming with record structures works by encoding a structure the first time it is seen in a stream and referencing the structure in later messages that are sent across that stream. When an encoder can expect a decoder to understand previous structure references, this can be configured using the `sequential: true` flag, which is auto-enabled by streams, but can also be used with Packr instances.
|
|
100
|
+
|
|
99
101
|
### Shared Record Structures
|
|
100
102
|
Another useful way of using cbor-x, and the record extension, is for storing data in a databases, files, or other storage systems. If a number of objects with common data structures are being stored, a shared structure can be used to greatly improve data storage and deserialization efficiency. In the simplest form, provide a `structures` array, which is updated if any new object structure is encountered:
|
|
101
103
|
|
|
@@ -119,35 +121,43 @@ let encoder = new Encoder({
|
|
|
119
121
|
structures: []
|
|
120
122
|
});
|
|
121
123
|
```
|
|
122
|
-
Cbor-x will automatically add and saves structures as it encounters any new object structures (up to a limit of
|
|
124
|
+
Cbor-x will automatically add and saves structures as it encounters any new object structures (up to a limit of 64). It will always add structures in incremental/compatible way: Any object encoded with an earlier structure can be decoded with a later version (as long as it is persisted).
|
|
123
125
|
|
|
124
126
|
### Reading Multiple Values
|
|
125
|
-
If you have a buffer with multiple values sequentially encoded, you can choose to parse and read multiple values. This can be done using the `
|
|
127
|
+
If you have a buffer with multiple values sequentially encoded, you can choose to parse and read multiple values. This can be done using the `decodeMultiple` function/method, which can return an array of all the values it can sequentially parse within the provided buffer. For example:
|
|
126
128
|
```js
|
|
127
129
|
let data = new Uint8Array([1, 2, 3]) // encodings of values 1, 2, and 3
|
|
128
|
-
let values =
|
|
130
|
+
let values = decodeMultiple(data) // [1, 2, 3]
|
|
129
131
|
```
|
|
130
132
|
Alternately, you can provide a callback function that is called as the parsing occurs with each value, and can optionally terminate the parsing by returning `false`:
|
|
131
133
|
```js
|
|
132
134
|
let data = new Uint8Array([1, 2, 3]) // encodings of values 1, 2, and 3
|
|
133
|
-
|
|
135
|
+
decodeMultiple(data, (value) => {
|
|
134
136
|
// called for each value
|
|
135
137
|
// return false if you wish to end the parsing
|
|
136
138
|
})
|
|
137
139
|
```
|
|
138
140
|
|
|
141
|
+
CBOR Packing
|
|
142
|
+
[Packed CBOR](https://datatracker.ietf.org/doc/html/draft-ietf-cbor-packed) is additional specification for CBOR which allows for compact encoding of data that has repeated values. Cbor-x supports decoding packed CBOR, no or flags/options needed. Cbor-x can also optionally generate packed CBOR (with the `pack` option), which will cause the encoder to look for repeated strings in a data structure that is being encoded, and store the strings in a packed table that can be referenced, to reduce encoding size. This involves extra overhead and reduces encoding performance, and generally does not yield as much compaction as standard compression tools. However, this is can be much faster than encoding plus compression, while still providing some level of reduction in encoding size. In addition to size reduction, packed CBOR is also usually faster to decode (assuming that some repetitive values could be found/packed).
|
|
143
|
+
|
|
144
|
+
Cbor-x also has in-progress effort to support shared packed tables.
|
|
145
|
+
|
|
139
146
|
## Options
|
|
140
147
|
The following options properties can be provided to the Encoder or Decoder constructor:
|
|
141
148
|
|
|
142
|
-
* `useRecords` - Setting this to `false` disables the record extension and stores JavaScript objects as CBOR maps, and decodes maps as JavaScript `Object`s, which ensures compatibilty with other decoders.
|
|
143
|
-
* `structures` - Provides the array of structures that is to be used for record extension, if you want the structures saved and used again. This array will be modified in place with new record structures that are serialized (if less than
|
|
149
|
+
* `useRecords` - Setting this to `false` disables the record extension and stores JavaScript objects as CBOR maps (with tag 259), and decodes maps as JavaScript `Object`s, which ensures compatibilty with other decoders.
|
|
150
|
+
* `structures` - Provides the array of structures that is to be used for record extension, if you want the structures saved and used again. This array will be modified in place with new record structures that are serialized (if less than 64 structures are in the array).
|
|
144
151
|
* `structuredClone` - This enables the structured cloning extensions that will encode object/cyclic references and additional built-in types/classes.
|
|
145
|
-
* `mapsAsObjects` - If `true`, this will decode CBOR maps and JS `Object`s with the map entries decoded to object properties. If `false`, maps are decoded as JavaScript `Map`s. This is disabled by default if `useRecords` is enabled (
|
|
146
|
-
* `useFloat32` - This will enable cbor-x to encode non-integer numbers as
|
|
147
|
-
* `
|
|
148
|
-
* `
|
|
152
|
+
* `mapsAsObjects` - If `true`, this will decode CBOR maps and JS `Object`s with the map entries decoded to object properties. If `false`, maps are decoded as JavaScript `Map`s. This is disabled by default if `useRecords` is enabled (`Map`s are preserved since they are distinct from records), and is enabled by default if `useRecords` is disabled.
|
|
153
|
+
* `useFloat32` - This will enable cbor-x to encode non-integer numbers as 32-bit (4 byte) floating point numbers. See next section for possible values.
|
|
154
|
+
* `pack` - This will enable [CBOR packing](https://datatracker.ietf.org/doc/html/draft-ietf-cbor-packed) for encoding, as described above.
|
|
155
|
+
* `variableMapSize` - This will use varying map size definition (from single-byte to full 32-bit representation) based on the number of keys when encoding objects, which yields slightly more compact encodings (for small objects), but is typically 5-10% slower during encoding. This is only relevant when record extension is disabled.
|
|
156
|
+
* `copyBuffers` - When decoding a CBOR message with binary data (Buffers are encoded as binary data), copy the buffer rather than providing a slice/view of the buffer. If you want your input data to be collected or modified while the decoded embedded buffer continues to live on, you can use this option (there is extra overhead to copying).
|
|
149
157
|
* `useTimestamp32` - Encode JS `Date`s in 32-bit format when possible by dropping the milliseconds. This is a more efficient encoding of dates. You can also cause dates to use 32-bit format by manually setting the milliseconds to zero (`date.setMilliseconds(0)`).
|
|
158
|
+
* `sequential` - Encode structures in serialized data, and reference previously encoded structures with expectation that decoder will read the encoded structures in the same order as encoded, with `unpackMultiple`.
|
|
150
159
|
* `largeBigIntToFloat` - If a bigint needs to be encoded that is larger than will fit in 64-bit integers, it will be encoded as a float-64 (otherwise will throw a RangeError).
|
|
160
|
+
* `useTag259ForMaps` - This flag indicates if [tag 259 (explicit maps)](https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec--explicit-maps.md) should be used to encode JS `Map`s. When using records is enabled, this is disabled by default, since plain objects are encoded with record structures and unambigiously differentiated from `Map`s, which are encoded as CBOR maps. Without using records, this enabled by default and is necessary to distinguish plain objects from `Map`s (but can be disabled by setting this to `false`).
|
|
151
161
|
|
|
152
162
|
### 32-bit Float Options
|
|
153
163
|
By default all non-integer numbers are serialized as 64-bit float (double). This is fast, and ensures maximum precision. However, often real-world data doesn't not need 64-bits of precision, and using 32-bit encoding can be much more space efficient. There are several options that provide more efficient encodings. Using the decimal rounding options for encoding and decoding provides lossless storage of common decimal representations like 7.99, in more efficient 32-bit format (rather than 64-bit). The `useFloat32` property has several possible options, available from the module as constants:
|
|
@@ -157,10 +167,12 @@ import { ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } from 'cbor-x'
|
|
|
157
167
|
|
|
158
168
|
* `ALWAYS` (1) - Always will encode non-integers (absolute less than 2147483648) as 32-bit float.
|
|
159
169
|
* `DECIMAL_ROUND` (3) - Always will encode non-integers as 32-bit float, and when decoding 32-bit float, round to the significant decimal digits (usually 7, but 6 or 8 digits for some ranges).
|
|
160
|
-
* `DECIMAL_FIT` (4) - Only encode non-integers as 32-bit float if all significant digits (usually up to 7) can be unamiguously encoded as a 32-bit float, and decode with decimal rounding (same as above). This will ensure round-trip encoding/decoding without loss in precision and
|
|
170
|
+
* `DECIMAL_FIT` (4) - Only encode non-integers as 32-bit float if all significant digits (usually up to 7) can be unamiguously encoded as a 32-bit float, and decode with decimal rounding (same as above). This will ensure round-trip encoding/decoding without loss in precision and uses 32-bit when possible.
|
|
161
171
|
|
|
162
172
|
Note, that the performance is decreased with decimal rounding by about 20-25%, although if only 5% of your values are floating point, that will only have about a 1% impact overall.
|
|
163
173
|
|
|
174
|
+
In addition, msgpackr exports a `roundFloat32(number)` function that can be used to round floating point numbers to the maximum significant decimal digits that can be stored in 32-bit float, just as DECIMAL_ROUND does when decoding. This can be useful for determining how a number will be decoded prior to encoding it.
|
|
175
|
+
|
|
164
176
|
## Performance
|
|
165
177
|
Cbor-x is fast. Really fast. Here is comparison with the next fastest JS projects using the benchmark tool from `msgpack-lite` (and the sample data is from some clinical research data we use that has a good mix of different value types and structures). It also includes comparison to V8 native JSON functionality, and JavaScript Avro (`avsc`, a very optimized Avro implementation):
|
|
166
178
|
|
|
@@ -243,14 +255,38 @@ During the serialization process, data is written to buffers. Again, allocating
|
|
|
243
255
|
Cbor-x currently uses tag id 105 and 26880-27135 for its [proposed extension for records](https://github.com/kriszyp/cbor-records).
|
|
244
256
|
|
|
245
257
|
### Dates
|
|
246
|
-
|
|
258
|
+
Cbor-x saves all JavaScript `Date`s using the standard CBOR date extension (tag 1).
|
|
247
259
|
|
|
248
260
|
### Structured Cloning
|
|
249
261
|
With structured cloning enabled, cbor-x will also use tags/extensions to store Set, Map, Error, RegExp, ArrayBufferView objects and preserve their types.
|
|
250
262
|
|
|
263
|
+
### List of supported tags for decoding
|
|
264
|
+
Here is a list of CBOR tags that are supported for decoding:
|
|
265
|
+
|
|
266
|
+
* 0 - String date
|
|
267
|
+
* 1 - Numeric Date
|
|
268
|
+
* 2 - BigInt
|
|
269
|
+
* 3 - Negative BigInt
|
|
270
|
+
* 27 - Generic named objects (used for Error, RegExp)
|
|
271
|
+
* 64 - Uint8Array
|
|
272
|
+
* 68 - Uint8ClampedArray
|
|
273
|
+
* 69 - Uint16Array
|
|
274
|
+
* 70 - Uint32Array
|
|
275
|
+
* 71 - BigUint64Array
|
|
276
|
+
* 72 - Int8Array
|
|
277
|
+
* 77 - Int16Array
|
|
278
|
+
* 78 - Int32Array
|
|
279
|
+
* 79 - BigInt64Array
|
|
280
|
+
* 81 - Float32Array
|
|
281
|
+
* 82 - Float64Array
|
|
282
|
+
* 105 - Records
|
|
283
|
+
* 258 - Set
|
|
284
|
+
* 259 - Map
|
|
285
|
+
* 40009, 40010 - Pointers for cycles
|
|
286
|
+
|
|
251
287
|
## Alternate Encoding/Package
|
|
252
288
|
The high-performance serialization and deserialization algorithms in this package are also available in the [msgpackr](https://github.com/kriszyp/msgpackr) for the MessagePack format, with the same API and design. A quick summary of the pros and cons of using MessagePack vs CBOR are:
|
|
253
|
-
* MessagePack has wider adoption, and, at least with this implementation is slightly more efficient (by roughly
|
|
289
|
+
* MessagePack has wider adoption, and, at least with this implementation is slightly more efficient (by roughly 1%, but YMMV).
|
|
254
290
|
* CBOR has an [official IETF standardization track](https://www.rfc-editor.org/rfc/rfc8949.html), and the record extensions is conceptually/philosophically a better fit for CBOR tags.
|
|
255
291
|
|
|
256
292
|
## License
|
|
@@ -258,7 +294,7 @@ The high-performance serialization and deserialization algorithms in this packag
|
|
|
258
294
|
MIT
|
|
259
295
|
|
|
260
296
|
### Browser Consideration
|
|
261
|
-
CBOR can be a great choice for high-performance data delivery to browsers, as reasonable data size is possible without compression. And CBOR
|
|
297
|
+
CBOR can be a great choice for high-performance data delivery to browsers, as reasonable data size is possible without compression. And CBOR works very well in modern browsers. However, it is worth noting that if you want highly compact data, brotli or gzip are most effective in compressing, and CBOR's character frequency tends to defeat Huffman encoding used by these standard compression algorithms, often resulting in less compact data than compressed JSON.
|
|
262
298
|
|
|
263
299
|
### Credits
|
|
264
300
|
|
package/SECURITY.md
ADDED
|
@@ -5,7 +5,7 @@ export enum FLOAT32_OPTIONS {
|
|
|
5
5
|
DECIMAL_FIT = 4
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
interface Options {
|
|
8
|
+
export interface Options {
|
|
9
9
|
useFloat32?: FLOAT32_OPTIONS
|
|
10
10
|
useRecords?: boolean
|
|
11
11
|
structures?: {}[]
|
|
@@ -15,24 +15,25 @@ interface Options {
|
|
|
15
15
|
copyBuffers?: boolean
|
|
16
16
|
useTimestamp32?: boolean
|
|
17
17
|
largeBigIntToFloat?: boolean
|
|
18
|
+
encodeUndefinedAsNil?: boolean
|
|
19
|
+
maxSharedStructures?: number
|
|
20
|
+
maxOwnStructures?: number
|
|
21
|
+
shouldShareStructure?: (keys: string[]) => boolean
|
|
18
22
|
getStructures?(): {}[]
|
|
19
23
|
saveStructures?(structures: {}[]): boolean | void
|
|
20
24
|
}
|
|
21
25
|
interface Extension {
|
|
22
26
|
Class: Function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
tag: number
|
|
28
|
+
encode(value: any): Buffer | Uint8Array
|
|
29
|
+
decode(messagePack: Buffer | Uint8Array): any
|
|
26
30
|
}
|
|
27
|
-
export class
|
|
31
|
+
export class Decoder {
|
|
28
32
|
constructor(options?: Options)
|
|
29
|
-
unpack(messagePack: Buffer | Uint8Array): any
|
|
30
33
|
decode(messagePack: Buffer | Uint8Array): any
|
|
31
|
-
|
|
34
|
+
decodeMultiple(messagePack: Buffer | Uint8Array, forEach?: (value: any) => any): [] | void
|
|
32
35
|
}
|
|
33
|
-
export class Decoder extends Unpackr {}
|
|
34
|
-
export function unpack(messagePack: Buffer | Uint8Array): any
|
|
35
|
-
export function unpackMultiple(messagePack: Buffer | Uint8Array, forEach?: (value: any) => any): [] | void
|
|
36
36
|
export function decode(messagePack: Buffer | Uint8Array): any
|
|
37
|
+
export function decodeMultiple(messagePack: Buffer | Uint8Array, forEach?: (value: any) => any): [] | void
|
|
37
38
|
export function addExtension(extension: Extension): void
|
|
38
|
-
export
|
|
39
|
+
export function roundFloat32(float32Number: number): number
|
package/decode.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict"
|
|
2
1
|
let decoder
|
|
3
2
|
try {
|
|
4
3
|
decoder = new TextDecoder()
|
|
@@ -9,6 +8,8 @@ let position = 0
|
|
|
9
8
|
let alreadySet
|
|
10
9
|
const EMPTY_ARRAY = []
|
|
11
10
|
const RECORD_TAG_ID = 0x69
|
|
11
|
+
const PACKED_TABLE_TAG_ID = 51
|
|
12
|
+
const PACKED_REFERENCE_TAG_ID = 6
|
|
12
13
|
const STOP_CODE = {}
|
|
13
14
|
let strings = EMPTY_ARRAY
|
|
14
15
|
let stringPosition = 0
|
|
@@ -19,11 +20,16 @@ let srcStringStart = 0
|
|
|
19
20
|
let srcStringEnd = 0
|
|
20
21
|
let referenceMap
|
|
21
22
|
let currentExtensions = []
|
|
23
|
+
let currentExtensionRanges = []
|
|
24
|
+
let packedValues
|
|
22
25
|
let dataView
|
|
26
|
+
let restoreMapsAsObject
|
|
27
|
+
let sharedValues
|
|
23
28
|
let defaultOptions = {
|
|
24
29
|
useRecords: false,
|
|
25
30
|
mapsAsObjects: true
|
|
26
31
|
}
|
|
32
|
+
let sequentialMode = false
|
|
27
33
|
|
|
28
34
|
export class Decoder {
|
|
29
35
|
constructor(options) {
|
|
@@ -35,12 +41,12 @@ export class Decoder {
|
|
|
35
41
|
}
|
|
36
42
|
Object.assign(this, options)
|
|
37
43
|
}
|
|
38
|
-
decode(source, end
|
|
44
|
+
decode(source, end) {
|
|
39
45
|
if (src) {
|
|
40
46
|
// re-entrant execution, save the state and restore it after we do this decode
|
|
41
47
|
return saveState(() => {
|
|
42
48
|
clearSource()
|
|
43
|
-
return this ? this.decode(source, end
|
|
49
|
+
return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
|
|
44
50
|
})
|
|
45
51
|
}
|
|
46
52
|
srcEnd = end > -1 ? end : source.length
|
|
@@ -56,19 +62,12 @@ export class Decoder {
|
|
|
56
62
|
dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength))
|
|
57
63
|
if (this) {
|
|
58
64
|
currentDecoder = this
|
|
65
|
+
packedValues = this.sharedValues &&
|
|
66
|
+
(this.pack ? new Array(this.maxPrivatePackedValues || 16).concat(this.sharedValues) :
|
|
67
|
+
this.sharedValues)
|
|
59
68
|
if (this.structures) {
|
|
60
69
|
currentStructures = this.structures
|
|
61
|
-
|
|
62
|
-
return read()
|
|
63
|
-
} finally {
|
|
64
|
-
if (position >= srcEnd || !continueReading) {
|
|
65
|
-
// finished reading this source, cleanup references
|
|
66
|
-
currentStructures = null
|
|
67
|
-
src = null
|
|
68
|
-
if (referenceMap)
|
|
69
|
-
referenceMap = null
|
|
70
|
-
}
|
|
71
|
-
}
|
|
70
|
+
return checkedRead()
|
|
72
71
|
} else if (!currentStructures || currentStructures.length > 0) {
|
|
73
72
|
currentStructures = []
|
|
74
73
|
}
|
|
@@ -76,27 +75,23 @@ export class Decoder {
|
|
|
76
75
|
currentDecoder = defaultOptions
|
|
77
76
|
if (!currentStructures || currentStructures.length > 0)
|
|
78
77
|
currentStructures = []
|
|
78
|
+
packedValues = null
|
|
79
79
|
}
|
|
80
|
-
|
|
81
|
-
return read()
|
|
82
|
-
} finally {
|
|
83
|
-
if (position >= srcEnd || !continueReading) {
|
|
84
|
-
src = null
|
|
85
|
-
if (referenceMap)
|
|
86
|
-
referenceMap = null
|
|
87
|
-
}
|
|
88
|
-
}
|
|
80
|
+
return checkedRead()
|
|
89
81
|
}
|
|
90
82
|
decodeMultiple(source, forEach) {
|
|
83
|
+
let values, lastPosition = 0
|
|
91
84
|
try {
|
|
92
|
-
let decoder = this
|
|
93
85
|
let size = source.length
|
|
94
|
-
|
|
95
|
-
let
|
|
86
|
+
sequentialMode = true
|
|
87
|
+
let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size)
|
|
96
88
|
if (forEach) {
|
|
97
|
-
forEach(value)
|
|
89
|
+
if (forEach(value) === false) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
98
92
|
while(position < size) {
|
|
99
|
-
|
|
93
|
+
lastPosition = position
|
|
94
|
+
if (forEach(checkedRead()) === false) {
|
|
100
95
|
return
|
|
101
96
|
}
|
|
102
97
|
}
|
|
@@ -104,11 +99,17 @@ export class Decoder {
|
|
|
104
99
|
else {
|
|
105
100
|
values = [ value ]
|
|
106
101
|
while(position < size) {
|
|
107
|
-
|
|
102
|
+
lastPosition = position
|
|
103
|
+
values.push(checkedRead())
|
|
108
104
|
}
|
|
109
105
|
return values
|
|
110
106
|
}
|
|
107
|
+
} catch(error) {
|
|
108
|
+
error.lastPosition = lastPosition
|
|
109
|
+
error.values = values
|
|
110
|
+
throw error
|
|
111
111
|
} finally {
|
|
112
|
+
sequentialMode = false
|
|
112
113
|
clearSource()
|
|
113
114
|
}
|
|
114
115
|
}
|
|
@@ -116,6 +117,33 @@ export class Decoder {
|
|
|
116
117
|
export function getPosition() {
|
|
117
118
|
return position
|
|
118
119
|
}
|
|
120
|
+
export function checkedRead() {
|
|
121
|
+
try {
|
|
122
|
+
let result = read()
|
|
123
|
+
if (position == srcEnd) {
|
|
124
|
+
// finished reading this source, cleanup references
|
|
125
|
+
currentStructures = null
|
|
126
|
+
src = null
|
|
127
|
+
if (referenceMap)
|
|
128
|
+
referenceMap = null
|
|
129
|
+
} else if (position > srcEnd) {
|
|
130
|
+
// over read
|
|
131
|
+
let error = new Error('Unexpected end of CBOR data')
|
|
132
|
+
error.incomplete = true
|
|
133
|
+
throw error
|
|
134
|
+
} else if (!sequentialMode) {
|
|
135
|
+
throw new Error('Data read, but end of buffer not reached')
|
|
136
|
+
}
|
|
137
|
+
// else more to read, but we are reading sequentially, so don't clear source yet
|
|
138
|
+
return result
|
|
139
|
+
} catch(error) {
|
|
140
|
+
clearSource()
|
|
141
|
+
if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
|
|
142
|
+
error.incomplete = true
|
|
143
|
+
}
|
|
144
|
+
throw error
|
|
145
|
+
}
|
|
146
|
+
}
|
|
119
147
|
|
|
120
148
|
export function read() {
|
|
121
149
|
let token = src[position++]
|
|
@@ -180,6 +208,10 @@ export function read() {
|
|
|
180
208
|
object[key] = read()
|
|
181
209
|
return object
|
|
182
210
|
} else {
|
|
211
|
+
if (restoreMapsAsObject) {
|
|
212
|
+
currentDecoder.mapsAsObjects = true
|
|
213
|
+
restoreMapsAsObject = false
|
|
214
|
+
}
|
|
183
215
|
let map = new Map()
|
|
184
216
|
while ((key = read()) != STOP_CODE)
|
|
185
217
|
map.set(key, read())
|
|
@@ -226,6 +258,10 @@ export function read() {
|
|
|
226
258
|
}
|
|
227
259
|
return object
|
|
228
260
|
} else {
|
|
261
|
+
if (restoreMapsAsObject) {
|
|
262
|
+
currentDecoder.mapsAsObjects = true
|
|
263
|
+
restoreMapsAsObject = false
|
|
264
|
+
}
|
|
229
265
|
let map = new Map()
|
|
230
266
|
for (let i = 0; i < token; i++) {
|
|
231
267
|
map.set(read(), read())
|
|
@@ -265,9 +301,15 @@ export function read() {
|
|
|
265
301
|
return extension(read)
|
|
266
302
|
else
|
|
267
303
|
return extension(read())
|
|
304
|
+
} else {
|
|
305
|
+
let input = read()
|
|
306
|
+
for (let i = 0; i < currentExtensionRanges.length; i++) {
|
|
307
|
+
let value = currentExtensionRanges[i](token, input)
|
|
308
|
+
if (value !== undefined)
|
|
309
|
+
return value
|
|
310
|
+
}
|
|
311
|
+
return new Tag(input)
|
|
268
312
|
}
|
|
269
|
-
else
|
|
270
|
-
return new Tag(read())
|
|
271
313
|
}
|
|
272
314
|
case 7: // fixed value
|
|
273
315
|
switch (token) {
|
|
@@ -277,6 +319,9 @@ export function read() {
|
|
|
277
319
|
case 0x17: return; // undefined
|
|
278
320
|
case 0x1f:
|
|
279
321
|
default:
|
|
322
|
+
let packedValue = packedValues[token]
|
|
323
|
+
if (packedValue !== undefined)
|
|
324
|
+
return packedValue
|
|
280
325
|
throw new Error('Unknown token ' + token)
|
|
281
326
|
}
|
|
282
327
|
default: // negative int
|
|
@@ -713,6 +758,23 @@ currentExtensions[RECORD_TAG_ID] = recordDefinition
|
|
|
713
758
|
currentExtensions[27] = (data) => { // http://cbor.schmorp.de/generic-object
|
|
714
759
|
return (glbl[data[0]] || Error)(data[1], data[2])
|
|
715
760
|
}
|
|
761
|
+
const packedTable = (read) => {
|
|
762
|
+
if (src[position++] != 0x84)
|
|
763
|
+
throw new Error('Packed values structure must be followed by 4 element array')
|
|
764
|
+
let newPackedValues = read() // packed values
|
|
765
|
+
packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues
|
|
766
|
+
packedValues.prefixes = read()
|
|
767
|
+
packedValues.suffixes = read()
|
|
768
|
+
return read() // read the rump
|
|
769
|
+
}
|
|
770
|
+
packedTable.handlesRead = true
|
|
771
|
+
currentExtensions[51] = packedTable
|
|
772
|
+
|
|
773
|
+
currentExtensions[PACKED_REFERENCE_TAG_ID] = (data) => { // packed reference
|
|
774
|
+
if (typeof data == 'number')
|
|
775
|
+
return packedValues[16 + (data >= 0 ? 2 * data : (-2 * data - 1))]
|
|
776
|
+
throw new Error('No support for non-integer packed references yet')
|
|
777
|
+
}
|
|
716
778
|
|
|
717
779
|
currentExtensions[40009] = (id) => {
|
|
718
780
|
// id extension (for structured clones)
|
|
@@ -743,7 +805,37 @@ currentExtensions[40010] = (id) => {
|
|
|
743
805
|
return refEntry.target
|
|
744
806
|
}
|
|
745
807
|
|
|
746
|
-
currentExtensions[258] = (array) => new Set(array) // https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md
|
|
808
|
+
currentExtensions[258] = (array) => new Set(array); // https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md
|
|
809
|
+
(currentExtensions[259] = (read) => {
|
|
810
|
+
// https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec
|
|
811
|
+
// for decoding as a standard Map
|
|
812
|
+
if (currentDecoder.mapsAsObjects) {
|
|
813
|
+
currentDecoder.mapsAsObjects = false
|
|
814
|
+
restoreMapsAsObject = true
|
|
815
|
+
}
|
|
816
|
+
return read()
|
|
817
|
+
}).handlesRead = true
|
|
818
|
+
function combine(a, b) {
|
|
819
|
+
if (typeof a === 'string')
|
|
820
|
+
return a + b
|
|
821
|
+
if (a instanceof Array)
|
|
822
|
+
return a.concat(b)
|
|
823
|
+
return Object.assign({}, a, b)
|
|
824
|
+
}
|
|
825
|
+
currentExtensionRanges.push((tag, input) => {
|
|
826
|
+
if (tag >= 225 && tag <= 255)
|
|
827
|
+
return combine(packedTable.prefixes[tag - 224], input)
|
|
828
|
+
if (tag >= 28704 && tag <= 32767)
|
|
829
|
+
return combine(packedTable.prefixes[tag - 28672], input)
|
|
830
|
+
if (tag >= 1879052288 && tag <= 2147483647)
|
|
831
|
+
return combine(packedTable.prefixes[tag - 1879048192], input)
|
|
832
|
+
if (tag >= 216 && tag <= 223)
|
|
833
|
+
return combine(input, packedTable.suffixes[tag - 216])
|
|
834
|
+
if (tag >= 27647 && tag <= 28671)
|
|
835
|
+
return combine(input, packedTable.suffixes[tag - 27639])
|
|
836
|
+
if (tag >= 1811940352 && tag <= 1879048191)
|
|
837
|
+
return combine(input, packedTable.suffixes[tag - 1811939328])
|
|
838
|
+
})
|
|
747
839
|
|
|
748
840
|
export const typedArrays = ['Uint8', 'Uint8Clamped', 'Uint16', 'Uint32', 'BigUint64','Int8', 'Int16', 'Int32', 'BigInt64', 'Float32', 'Float64'].map(type => type + 'Array')
|
|
749
841
|
const typedArrayTags = [64, 68, 69, 70, 71, 72, 77, 78, 79, 81, 82]
|
|
@@ -772,6 +864,7 @@ function saveState(callback) {
|
|
|
772
864
|
let savedSrc = new Uint8Array(src.slice(0, srcEnd)) // we copy the data in case it changes while external data is processed
|
|
773
865
|
let savedStructures = currentStructures
|
|
774
866
|
let savedDecoder = currentDecoder
|
|
867
|
+
let savedSequentialMode = sequentialMode
|
|
775
868
|
let value = callback()
|
|
776
869
|
srcEnd = savedSrcEnd
|
|
777
870
|
position = savedPosition
|
|
@@ -782,6 +875,7 @@ function saveState(callback) {
|
|
|
782
875
|
strings = savedStrings
|
|
783
876
|
referenceMap = savedReferenceMap
|
|
784
877
|
src = savedSrc
|
|
878
|
+
sequentialMode = savedSequentialMode
|
|
785
879
|
currentStructures = savedStructures
|
|
786
880
|
currentDecoder = savedDecoder
|
|
787
881
|
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
|
|
@@ -797,7 +891,7 @@ export function addExtension(extension) {
|
|
|
797
891
|
currentExtensions[extension.tag] = extension.decode
|
|
798
892
|
}
|
|
799
893
|
|
|
800
|
-
export
|
|
894
|
+
export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
|
|
801
895
|
for (let i = 0; i < 256; i++) {
|
|
802
896
|
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103))
|
|
803
897
|
}
|
|
@@ -812,3 +906,10 @@ export const FLOAT32_OPTIONS = {
|
|
|
812
906
|
DECIMAL_ROUND: 3,
|
|
813
907
|
DECIMAL_FIT: 4
|
|
814
908
|
}
|
|
909
|
+
let f32Array = new Float32Array(1)
|
|
910
|
+
let u8Array = new Uint8Array(f32Array.buffer, 0, 4)
|
|
911
|
+
export function roundFloat32(float32Number) {
|
|
912
|
+
f32Array[0] = float32Number
|
|
913
|
+
let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]
|
|
914
|
+
return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
|
|
915
|
+
}
|