cbor-x 0.8.0 → 0.9.0

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.
@@ -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
  [![types](https://img.shields.io/npm/types/cbor-x)](README.md)
7
7
  [![module](https://img.shields.io/badge/module-ESM%2FCJS-blue)](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,10 +48,10 @@ receivingStream.on('data', (data) => {
48
48
  // received data
49
49
  });
50
50
  ```
51
- The `EncoderStream` and `DecoderStream` instances will have also the record structure extension enabled by default (see below).
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
- Msgpackr modules are standard ESM modules and can be loaded directly from github (https://raw.githubusercontent.com/kriszyp/msgpackr/master/index.js) or downloaded and used directly in Deno. The standard pack/encode and unpack/decode functionality is available on Deno, like other platforms.
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.
55
55
 
56
56
  ## Browser Usage
57
57
  Cbor-x works as standalone JavaScript as well, and runs on modern browsers. It includes a bundled script, at `dist/index.js` for ease of direct loading:
@@ -82,10 +82,10 @@ copy.set.has('a') // true
82
82
 
83
83
  ```
84
84
 
85
- This option is disabled by default because it uses extensions and 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.)
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. 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:
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,18 +121,18 @@ 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 32). 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).
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
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 `unpackMultiple` 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 = unpackMultiple(data) // [1, 2, 3]
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
- unpackMultiple(data, (value) => {
135
+ decodeMultiple(data, (value) => {
134
136
  // called for each value
135
137
  // return false if you wish to end the parsing
136
138
  })
@@ -139,15 +141,17 @@ unpackMultiple(data, (value) => {
139
141
  ## Options
140
142
  The following options properties can be provided to the Encoder or Decoder constructor:
141
143
 
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 32 structures are in the array).
144
+ * `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.
145
+ * `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
146
  * `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 (which allows `Map`s to be preserved), and is enabled by default if `useRecords` is disabled.
146
- * `useFloat32` - This will enable cbor-x to encode non-integer numbers as `float32`. See next section for possible values.
147
- * `variableMapSize` - This will use varying map size definition (fixmap, map16, map32) 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.
148
- * `copyBuffers` - When decoding a CBOR 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).
147
+ * `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.
148
+ * `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.
149
+ * `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.
150
+ * `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
151
  * `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)`).
152
+ * `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
153
  * `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).
154
+ * `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
155
 
152
156
  ### 32-bit Float Options
153
157
  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 +161,12 @@ import { ALWAYS, DECIMAL_ROUND, DECIMAL_FIT } from 'cbor-x'
157
161
 
158
162
  * `ALWAYS` (1) - Always will encode non-integers (absolute less than 2147483648) as 32-bit float.
159
163
  * `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 use 32-bit when possible.
164
+ * `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
165
 
162
166
  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
167
 
168
+ 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.
169
+
164
170
  ## Performance
165
171
  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
172
 
@@ -226,29 +232,6 @@ addExtension({
226
232
  }
227
233
  });
228
234
  ```
229
- If you want to use msgpackr to encode and decode the data within your extensions, you can use the `read` and `write` functions and read and write data/objects that will be encoded and decoded by msgpackr, which can be easier and faster than creating and receiving separate buffers (note that you can't just return the instance from `write` or msgpackr will recursively try to use extension infinitely):
230
- ```js
231
- import { addExtension, Packr } from 'msgpackr';
232
-
233
- class MyCustomClass {...}
234
-
235
- let extPackr = new Packr();
236
- addExtension({
237
- Class: MyCustomClass,
238
- type: 11, // register your own extension code (a type code from 1-100)
239
- write(instance) {
240
- // define how your custom class should be encoded
241
- return instance.myData; // return some data to be encoded
242
- }
243
- read(data) {
244
- // define how your custom class should be decoded,
245
- // data will already be unpacked/decoded
246
- let instance = new MyCustomClass();
247
- instance.myData = data;
248
- return instance; // return decoded value
249
- }
250
- });
251
- ```
252
235
 
253
236
  ## Unknown Tags
254
237
  If no extension is registered for a tag, the decoder will return an instance of the `Tag` class, where the value provided for the tag will be available in the `value` property of the `Tag` instance. The `Tag` class is an export of the package and decode module.
@@ -263,20 +246,41 @@ Cbor-x is already fast, but here are some tips for making it faster.
263
246
  During the serialization process, data is written to buffers. Again, allocating new buffers is a relatively expensive process, and the `useBuffer` method can help allow reuse of buffers that will further improve performance. With `useBuffer` method, you can provide a buffer, serialize data into it, and when it is known that you are done using that buffer, you can call `useBuffer` again to reuse it. The use of `useBuffer` is never required, buffers will still be handled and cleaned up through GC if not used, it just provides a small performance boost.
264
247
 
265
248
  ## Extensions
266
- Cbor-x currently uses tag ids 40000 to 40500 for its proposed extensions (until accepted).
267
-
268
- ## Record Structure Extension Definition
269
- The record struction extension uses tag 40006 to declare a new record structure. This is followed by an array where the first element indicates the tag id of the record structure to declare and the next element is an array of the field names, and the third element is array of the property values. The extension declaration must be immediately follow by the field names of the record structure.
249
+ Cbor-x currently uses tag id 105 and 26880-27135 for its [proposed extension for records](https://github.com/kriszyp/cbor-records).
270
250
 
271
251
  ### Dates
272
- cbor-x saves all JavaScript `Date`s using the standard CBOR date extension (tag 1).
252
+ Cbor-x saves all JavaScript `Date`s using the standard CBOR date extension (tag 1).
273
253
 
274
254
  ### Structured Cloning
275
255
  With structured cloning enabled, cbor-x will also use tags/extensions to store Set, Map, Error, RegExp, ArrayBufferView objects and preserve their types.
276
256
 
257
+ ### List of supported tags for decoding
258
+ Here is a list of CBOR tags that are supported for decoding:
259
+
260
+ * 0 - String date
261
+ * 1 - Numeric Date
262
+ * 2 - BigInt
263
+ * 3 - Negative BigInt
264
+ * 27 - Generic named objects (used for Error, RegExp)
265
+ * 64 - Uint8Array
266
+ * 68 - Uint8ClampedArray
267
+ * 69 - Uint16Array
268
+ * 70 - Uint32Array
269
+ * 71 - BigUint64Array
270
+ * 72 - Int8Array
271
+ * 77 - Int16Array
272
+ * 78 - Int32Array
273
+ * 79 - BigInt64Array
274
+ * 81 - Float32Array
275
+ * 82 - Float64Array
276
+ * 105 - Records
277
+ * 258 - Set
278
+ * 259 - Map
279
+ * 40009, 40010 - Pointers for cycles
280
+
277
281
  ## Alternate Encoding/Package
278
282
  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:
279
- * MessagePack has wider adoption, and, at least with this implementation is slightly more efficient (by roughly 2-4%).
283
+ * MessagePack has wider adoption, and, at least with this implementation is slightly more efficient (by roughly 1%, but YMMV).
280
284
  * 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.
281
285
 
282
286
  ## License
@@ -284,7 +288,7 @@ The high-performance serialization and deserialization algorithms in this packag
284
288
  MIT
285
289
 
286
290
  ### Browser Consideration
287
- CBOR can be a great choice for high-performance data delivery to browsers, as reasonable data size is possible without compression. And msgpackr 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, resulting in less compact data than compressed JSON.
291
+ 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.
288
292
 
289
293
  ### Credits
290
294
 
package/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 1.4.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ Please report security vulnerabilities to kriszyp@gmail.com.
@@ -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
- type: number
24
- pack(value: any): Buffer | Uint8Array
25
- unpack(messagePack: Buffer | Uint8Array): any
27
+ tag: number
28
+ encode(value: any): Buffer | Uint8Array
29
+ decode(messagePack: Buffer | Uint8Array): any
26
30
  }
27
- export class Unpackr {
31
+ export class Decoder {
28
32
  constructor(options?: Options)
29
- unpack(messagePack: Buffer | Uint8Array): any
30
33
  decode(messagePack: Buffer | Uint8Array): any
31
- unpackMultiple(messagePack: Buffer | Uint8Array, forEach?: (value: any) => any): [] | void
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 const C1: {}
39
+ export function roundFloat32(float32Number: number): number
package/decode.js CHANGED
@@ -20,10 +20,12 @@ let srcStringEnd = 0
20
20
  let referenceMap
21
21
  let currentExtensions = []
22
22
  let dataView
23
+ let restoreMapsAsObject
23
24
  let defaultOptions = {
24
25
  useRecords: false,
25
26
  mapsAsObjects: true
26
27
  }
28
+ let sequentialMode = false
27
29
 
28
30
  export class Decoder {
29
31
  constructor(options) {
@@ -35,12 +37,12 @@ export class Decoder {
35
37
  }
36
38
  Object.assign(this, options)
37
39
  }
38
- decode(source, end, continueReading) {
40
+ decode(source, end) {
39
41
  if (src) {
40
42
  // re-entrant execution, save the state and restore it after we do this decode
41
43
  return saveState(() => {
42
44
  clearSource()
43
- return this ? this.decode(source, end, continueReading) : Decoder.prototype.decode.call(defaultOptions, source, end, continueReading)
45
+ return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
44
46
  })
45
47
  }
46
48
  srcEnd = end > -1 ? end : source.length
@@ -58,17 +60,7 @@ export class Decoder {
58
60
  currentDecoder = this
59
61
  if (this.structures) {
60
62
  currentStructures = this.structures
61
- try {
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
- }
63
+ return checkedRead()
72
64
  } else if (!currentStructures || currentStructures.length > 0) {
73
65
  currentStructures = []
74
66
  }
@@ -77,26 +69,19 @@ export class Decoder {
77
69
  if (!currentStructures || currentStructures.length > 0)
78
70
  currentStructures = []
79
71
  }
80
- try {
81
- return read()
82
- } finally {
83
- if (position >= srcEnd || !continueReading) {
84
- src = null
85
- if (referenceMap)
86
- referenceMap = null
87
- }
88
- }
72
+ return checkedRead()
89
73
  }
90
74
  decodeMultiple(source, forEach) {
75
+ let values, lastPosition = 0
91
76
  try {
92
- let decoder = this
93
77
  let size = source.length
94
- let value = this ? this.decode(source, size, true) : defaultDecoder.decode(source, size, true)
95
- let values
78
+ sequentialMode = true
79
+ let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size)
96
80
  if (forEach) {
97
81
  forEach(value)
98
82
  while(position < size) {
99
- if (forEach(read()) === false) {
83
+ lastPosition = position
84
+ if (forEach(checkedRead()) === false) {
100
85
  return
101
86
  }
102
87
  }
@@ -104,11 +89,17 @@ export class Decoder {
104
89
  else {
105
90
  values = [ value ]
106
91
  while(position < size) {
107
- values.push(read())
92
+ lastPosition = position
93
+ values.push(checkedRead())
108
94
  }
109
95
  return values
110
96
  }
97
+ } catch(error) {
98
+ error.lastPosition = lastPosition
99
+ error.values = values
100
+ throw error
111
101
  } finally {
102
+ sequentialMode = false
112
103
  clearSource()
113
104
  }
114
105
  }
@@ -116,6 +107,33 @@ export class Decoder {
116
107
  export function getPosition() {
117
108
  return position
118
109
  }
110
+ export function checkedRead() {
111
+ try {
112
+ let result = read()
113
+ if (position == srcEnd) {
114
+ // finished reading this source, cleanup references
115
+ currentStructures = null
116
+ src = null
117
+ if (referenceMap)
118
+ referenceMap = null
119
+ } else if (position > srcEnd) {
120
+ // over read
121
+ let error = new Error('Unexpected end of CBOR data')
122
+ error.incomplete = true
123
+ throw error
124
+ } else if (!sequentialMode) {
125
+ throw new Error('Data read, but end of buffer not reached')
126
+ }
127
+ // else more to read, but we are reading sequentially, so don't clear source yet
128
+ return result
129
+ } catch(error) {
130
+ clearSource()
131
+ if (error instanceof RangeError || error.message.startsWith('Unexpected end of buffer')) {
132
+ error.incomplete = true
133
+ }
134
+ throw error
135
+ }
136
+ }
119
137
 
120
138
  export function read() {
121
139
  let token = src[position++]
@@ -180,6 +198,10 @@ export function read() {
180
198
  object[key] = read()
181
199
  return object
182
200
  } else {
201
+ if (restoreMapsAsObject) {
202
+ currentDecoder.mapsAsObjects = true
203
+ restoreMapsAsObject = false
204
+ }
183
205
  let map = new Map()
184
206
  while ((key = read()) != STOP_CODE)
185
207
  map.set(key, read())
@@ -226,6 +248,10 @@ export function read() {
226
248
  }
227
249
  return object
228
250
  } else {
251
+ if (restoreMapsAsObject) {
252
+ currentDecoder.mapsAsObjects = true
253
+ restoreMapsAsObject = false
254
+ }
229
255
  let map = new Map()
230
256
  for (let i = 0; i < token; i++) {
231
257
  map.set(read(), read())
@@ -339,10 +365,18 @@ export function setExtractor(extractStrings) {
339
365
  return function readString(length) {
340
366
  let string = strings[stringPosition++]
341
367
  if (string == null) {
342
- strings = extractStrings(position, srcEnd, length, src)
343
- stringPosition = 0
344
- srcStringEnd = 1 // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
345
- string = strings[stringPosition++]
368
+ let extraction = extractStrings(position, srcEnd, length, src)
369
+ if (typeof extraction == 'string') {
370
+ string = extraction
371
+ strings = EMPTY_ARRAY
372
+ } else {
373
+ strings = extraction
374
+ stringPosition = 1
375
+ srcStringEnd = 1 // even if a utf-8 string was decoded, must indicate we are in the midst of extracted strings and can't skip strings
376
+ string = strings[0]
377
+ if (string === undefined)
378
+ throw new Error('Unexpected end of buffer')
379
+ }
346
380
  }
347
381
  let srcStringLength = string.length
348
382
  if (srcStringLength <= length) {
@@ -735,7 +769,17 @@ currentExtensions[40010] = (id) => {
735
769
  return refEntry.target
736
770
  }
737
771
 
738
- currentExtensions[258] = (array) => new Set(array) // https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md
772
+ currentExtensions[258] = (array) => new Set(array); // https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md
773
+ (currentExtensions[259] = (read) => {
774
+ // https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec
775
+ // for decoding as a standard Map
776
+ if (currentDecoder.mapsAsObjects) {
777
+ currentDecoder.mapsAsObjects = false
778
+ restoreMapsAsObject = true
779
+ }
780
+ return read()
781
+ }).handlesRead = true
782
+
739
783
 
740
784
  export const typedArrays = ['Uint8', 'Uint8Clamped', 'Uint16', 'Uint32', 'BigUint64','Int8', 'Int16', 'Int32', 'BigInt64', 'Float32', 'Float64'].map(type => type + 'Array')
741
785
  const typedArrayTags = [64, 68, 69, 70, 71, 72, 77, 78, 79, 81, 82]
@@ -764,6 +808,7 @@ function saveState(callback) {
764
808
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)) // we copy the data in case it changes while external data is processed
765
809
  let savedStructures = currentStructures
766
810
  let savedDecoder = currentDecoder
811
+ let savedSequentialMode = sequentialMode
767
812
  let value = callback()
768
813
  srcEnd = savedSrcEnd
769
814
  position = savedPosition
@@ -774,6 +819,7 @@ function saveState(callback) {
774
819
  strings = savedStrings
775
820
  referenceMap = savedReferenceMap
776
821
  src = savedSrc
822
+ sequentialMode = savedSequentialMode
777
823
  currentStructures = savedStructures
778
824
  currentDecoder = savedDecoder
779
825
  dataView = new DataView(src.buffer, src.byteOffset, src.byteLength)
@@ -789,7 +835,7 @@ export function addExtension(extension) {
789
835
  currentExtensions[extension.tag] = extension.decode
790
836
  }
791
837
 
792
- export let mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
838
+ export const mult10 = new Array(147) // this is a table matching binary exponents to the multiplier to determine significant digit rounding
793
839
  for (let i = 0; i < 256; i++) {
794
840
  mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103))
795
841
  }
@@ -804,3 +850,10 @@ export const FLOAT32_OPTIONS = {
804
850
  DECIMAL_ROUND: 3,
805
851
  DECIMAL_FIT: 4
806
852
  }
853
+ let f32Array = new Float32Array(1)
854
+ let u8Array = new Uint8Array(f32Array.buffer, 0, 4)
855
+ export function roundFloat32(float32Number) {
856
+ f32Array[0] = float32Number
857
+ let multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)]
858
+ return ((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) / multiplier
859
+ }