cbor-x 1.6.8 → 1.6.9
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/README.md +4 -0
- package/decode.js +151 -28
- package/dist/decode-no-eval.cjs +152 -28
- package/dist/decode-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.cjs +540 -48
- package/dist/index-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.min.js +1 -1
- package/dist/index-no-eval.min.js.map +1 -1
- package/dist/index.js +540 -48
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/node.cjs +595 -100
- package/dist/node.cjs.map +1 -1
- package/dist/test.js +147 -30
- package/dist/test.js.map +1 -1
- package/encode.js +21 -2
- package/index.d.ts +1 -1
- package/iterators.js +123 -19
- package/package.json +2 -2
- package/scan.js +253 -0
- package/stream.js +9 -10
package/encode.js
CHANGED
|
@@ -572,6 +572,11 @@ export class Encoder extends Decoder {
|
|
|
572
572
|
return encode(json)
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
+
// not in extensions, so class instances that reach writeObject do not pay another instanceof
|
|
576
|
+
if (constructor === DataView) {
|
|
577
|
+
writeBuffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength), makeRoom)
|
|
578
|
+
return
|
|
579
|
+
}
|
|
575
580
|
// no extension found, write as a plain object
|
|
576
581
|
writeObject(value)
|
|
577
582
|
}
|
|
@@ -666,8 +671,22 @@ export class Encoder extends Decoder {
|
|
|
666
671
|
size++
|
|
667
672
|
}
|
|
668
673
|
}
|
|
669
|
-
|
|
670
|
-
|
|
674
|
+
if (size < 0x10000) {
|
|
675
|
+
// fits in a map-16 (uint16) count: back-patch the reserved 2 bytes
|
|
676
|
+
target[objectOffset++ + start] = size >> 8
|
|
677
|
+
target[objectOffset + start] = size & 0xff
|
|
678
|
+
} else {
|
|
679
|
+
// too many entries for a map-16 count: widen the already-written header
|
|
680
|
+
// to map-32, mirroring writeArrayHeader's 0x9a/setUint32 path. Shift the
|
|
681
|
+
// encoded entries 2 bytes forward to make room for the wider (uint32) count.
|
|
682
|
+
if (position + 2 > safeEnd)
|
|
683
|
+
makeRoom(position + 2)
|
|
684
|
+
let headerPosition = objectOffset + start
|
|
685
|
+
target.copyWithin(headerPosition + 4, headerPosition + 2, position)
|
|
686
|
+
position += 2
|
|
687
|
+
target[headerPosition - 1] = 0xba // map 32
|
|
688
|
+
targetView.setUint32(headerPosition, size)
|
|
689
|
+
}
|
|
671
690
|
} :
|
|
672
691
|
(object, skipValues) => {
|
|
673
692
|
let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null))
|
package/index.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ import { Transform, Readable } from 'stream'
|
|
|
66
66
|
|
|
67
67
|
export as namespace CBOR;
|
|
68
68
|
export class DecoderStream extends Transform {
|
|
69
|
-
constructor(options?: Options | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean })
|
|
69
|
+
constructor(options?: Options & { maxIncompleteBufferSize?: number } | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean, maxIncompleteBufferSize?: number })
|
|
70
70
|
}
|
|
71
71
|
export class EncoderStream extends Transform {
|
|
72
72
|
constructor(options?: Options | { highWaterMark: number, emitClose: boolean, allowHalfOpen: boolean })
|
package/iterators.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Encoder } from './encode.js'
|
|
2
|
-
import { Decoder } from './decode.js'
|
|
2
|
+
import { Decoder, _stream } from './decode.js'
|
|
3
|
+
import { StructureScanner } from './scan.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Given an Iterable first argument, returns an Iterable where each value is encoded as a Buffer
|
|
@@ -38,7 +39,7 @@ async function * encodeIterAsync (objectIterator, options) {
|
|
|
38
39
|
* Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
|
|
39
40
|
* Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
|
|
40
41
|
* @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
|
|
41
|
-
* @param {object} [options] - Decoder options
|
|
42
|
+
* @param {object} [options] - Decoder options, and maxIncompleteBufferSize
|
|
42
43
|
* @returns {IterableIterator|Promise.<AsyncIterableIterator}
|
|
43
44
|
*/
|
|
44
45
|
export function decodeIter (bufferIterator, options = {}) {
|
|
@@ -47,26 +48,15 @@ export function decodeIter (bufferIterator, options = {}) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const decoder = new Decoder(options)
|
|
50
|
-
|
|
51
|
+
const chunkDecoder = new ChunkDecoder(decoder, options.maxIncompleteBufferSize)
|
|
52
|
+
let fatalError
|
|
51
53
|
const parser = (chunk) => {
|
|
52
|
-
let yields
|
|
53
|
-
// if there's incomplete data from previous chunk, concatinate and try again
|
|
54
|
-
if (incomplete) {
|
|
55
|
-
chunk = Buffer.concat([incomplete, chunk])
|
|
56
|
-
incomplete = undefined
|
|
57
|
-
}
|
|
58
|
-
|
|
59
54
|
try {
|
|
60
|
-
|
|
61
|
-
} catch (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
incomplete = chunk.slice(err.lastPosition)
|
|
65
|
-
} else {
|
|
66
|
-
fatalError = err
|
|
67
|
-
}
|
|
55
|
+
return chunkDecoder.buffer ? chunkDecoder.decodeAfterIncomplete(chunk) : decoder.decodeMultiple(chunk)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
fatalError = chunkDecoder.keepIncomplete(error, chunk)
|
|
58
|
+
return (error && error.values) || []
|
|
68
59
|
}
|
|
69
|
-
return yields || []
|
|
70
60
|
}
|
|
71
61
|
|
|
72
62
|
if (typeof bufferIterator[Symbol.iterator] === 'function') {
|
|
@@ -75,6 +65,8 @@ export function decodeIter (bufferIterator, options = {}) {
|
|
|
75
65
|
yield * parser(value)
|
|
76
66
|
if (fatalError) throw fatalError
|
|
77
67
|
}
|
|
68
|
+
const endError = chunkDecoder.end()
|
|
69
|
+
if (endError) throw endError
|
|
78
70
|
})()
|
|
79
71
|
} else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
|
|
80
72
|
return (async function * iter () {
|
|
@@ -82,6 +74,118 @@ export function decodeIter (bufferIterator, options = {}) {
|
|
|
82
74
|
yield * parser(value)
|
|
83
75
|
if (fatalError) throw fatalError
|
|
84
76
|
}
|
|
77
|
+
const endError = chunkDecoder.end()
|
|
78
|
+
if (endError) throw endError
|
|
85
79
|
})()
|
|
86
80
|
}
|
|
87
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Keeps the bytes of a value that a chunk of a stream ends inside of, for DecoderStream and decodeIter, and decodes
|
|
85
|
+
* them again once enough have arrived to possibly complete it. Callers decode a chunk with decodeAfterIncomplete if
|
|
86
|
+
* buffer is set, or else directly with decodeMultiple, and pass any error to keepIncomplete, which returns the error
|
|
87
|
+
* to report, if any. end returns an error if the input ended inside a value.
|
|
88
|
+
*/
|
|
89
|
+
export class ChunkDecoder {
|
|
90
|
+
constructor (decoder, maxIncompleteBufferSize) {
|
|
91
|
+
this.decoder = decoder
|
|
92
|
+
if (maxIncompleteBufferSize === undefined) maxIncompleteBufferSize = 0x4000000
|
|
93
|
+
if (typeof maxIncompleteBufferSize !== 'number' || !(maxIncompleteBufferSize >= 0)) {
|
|
94
|
+
throw new TypeError('maxIncompleteBufferSize must be a non-negative number')
|
|
95
|
+
}
|
|
96
|
+
this.maxIncompleteBufferSize = Math.floor(maxIncompleteBufferSize)
|
|
97
|
+
// buffer[0, bufferEnd) is the incomplete value; no decoded value is a view of buffer while it is kept here
|
|
98
|
+
this.buffer = null
|
|
99
|
+
this.bufferEnd = 0
|
|
100
|
+
this.appends = 0
|
|
101
|
+
this.scanner = null
|
|
102
|
+
this.scanned = false
|
|
103
|
+
this.resetScanner = false
|
|
104
|
+
this.neededLength = 0 // a lower bound on the incomplete value's length
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
decodeAfterIncomplete (chunk) {
|
|
108
|
+
if (!(ArrayBuffer.isView(chunk) && chunk.BYTES_PER_ELEMENT === 1)) {
|
|
109
|
+
throw new TypeError('Source must be a Uint8Array or Buffer')
|
|
110
|
+
}
|
|
111
|
+
const length = this.bufferEnd + chunk.length
|
|
112
|
+
if (length < this.neededLength && length > this.maxIncompleteBufferSize) throw maxIncompleteBufferSizeError()
|
|
113
|
+
if (length > this.buffer.length) {
|
|
114
|
+
// Values usually complete within two appends, which are sized exactly. After that the buffer grows by at least
|
|
115
|
+
// the bytes already kept, so that they are copied a bounded number of times however small the chunks are.
|
|
116
|
+
const size = this.appends < 2 ? length
|
|
117
|
+
: Math.max(length, Math.min(length + this.bufferEnd, this.maxIncompleteBufferSize))
|
|
118
|
+
const buffer = Buffer.allocUnsafe(size)
|
|
119
|
+
if (size > length) buffer.fill(0, length) // decoded values are views of this buffer, so no uninitialized memory
|
|
120
|
+
buffer.set(this.buffer.subarray(0, this.bufferEnd))
|
|
121
|
+
this.buffer = buffer
|
|
122
|
+
}
|
|
123
|
+
this.buffer.set(chunk, this.bufferEnd)
|
|
124
|
+
this.bufferEnd = length
|
|
125
|
+
this.appends++
|
|
126
|
+
if (length < this.neededLength) return []
|
|
127
|
+
if (!this.scanner) this.scanner = new StructureScanner(this.decoder)
|
|
128
|
+
if (this.resetScanner) {
|
|
129
|
+
this.scanner.reset()
|
|
130
|
+
this.resetScanner = false
|
|
131
|
+
}
|
|
132
|
+
if (!this.scanner.scan(this.buffer, length)) {
|
|
133
|
+
this.neededLength = this.scanner.neededLength
|
|
134
|
+
if (length > this.maxIncompleteBufferSize) throw maxIncompleteBufferSizeError()
|
|
135
|
+
return []
|
|
136
|
+
}
|
|
137
|
+
this.scanned = true
|
|
138
|
+
const values = _stream.decode(this.decoder, this.buffer.subarray(0, length), this.scanner.position)
|
|
139
|
+
this.scanned = false
|
|
140
|
+
this.buffer = null
|
|
141
|
+
return values
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
keepIncomplete (error, chunk) {
|
|
145
|
+
// an extension may throw any value, and a falsy one would read as success
|
|
146
|
+
if (!error) return this.fail(new Error('Decoding failed: ' + error))
|
|
147
|
+
if (!error.incomplete) return this.fail(error)
|
|
148
|
+
if (this.scanned && error.lastPosition === 0) {
|
|
149
|
+
error.incomplete = false
|
|
150
|
+
return this.fail(error)
|
|
151
|
+
}
|
|
152
|
+
const source = this.buffer ? this.buffer.subarray(0, this.bufferEnd) : chunk
|
|
153
|
+
const length = source.length - error.lastPosition
|
|
154
|
+
if (length > this.maxIncompleteBufferSize) return this.fail(maxIncompleteBufferSizeError())
|
|
155
|
+
if (!this.buffer || error.lastPosition > 0) {
|
|
156
|
+
if (length === 0) return
|
|
157
|
+
try {
|
|
158
|
+
// copied, so that the remainder does not keep in memory the chunk, or the buffer that the values before it
|
|
159
|
+
// are views of
|
|
160
|
+
this.buffer = Buffer.from(source.subarray(error.lastPosition))
|
|
161
|
+
} catch (copyError) {
|
|
162
|
+
return this.fail(copyError)
|
|
163
|
+
}
|
|
164
|
+
this.bufferEnd = length
|
|
165
|
+
this.appends = 0
|
|
166
|
+
}
|
|
167
|
+
this.scanned = false
|
|
168
|
+
this.resetScanner = true
|
|
169
|
+
this.neededLength = Math.max(length + 1, error.neededEnd ? error.neededEnd - error.lastPosition : 0)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
end () {
|
|
173
|
+
if (this.buffer) {
|
|
174
|
+
const error = this.fail(new Error('Unexpected end of CBOR data'))
|
|
175
|
+
error.incomplete = true
|
|
176
|
+
return error
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fail (error) {
|
|
181
|
+
this.buffer = null
|
|
182
|
+
this.scanner = null
|
|
183
|
+
this.scanned = false
|
|
184
|
+
this.resetScanner = false
|
|
185
|
+
return error
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function maxIncompleteBufferSizeError () {
|
|
190
|
+
return new Error('Maximum incomplete buffer size exceeded')
|
|
191
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cbor-x",
|
|
3
3
|
"author": "Kris Zyp",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.9",
|
|
5
5
|
"description": "Ultra-fast and conformant CBOR (RFC 8949) implementation with support for numerous tag extensions including records and structured cloning",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"types": "./index.d.ts",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"node:buffer": false
|
|
76
76
|
},
|
|
77
77
|
"optionalDependencies": {
|
|
78
|
-
"cbor-extract": "^2.2.
|
|
78
|
+
"cbor-extract": "^2.2.3"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@rollup/plugin-json": "^5.0.1",
|
package/scan.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { _stream } from './decode.js'
|
|
2
|
+
|
|
3
|
+
// Positions are relative to the retained value, including bundle pointers. Frames and their storage survive appends.
|
|
4
|
+
export class StructureScanner {
|
|
5
|
+
constructor (decoder) {
|
|
6
|
+
this.decoder = decoder
|
|
7
|
+
this.frames = []
|
|
8
|
+
this.records = new Map()
|
|
9
|
+
this.readers = new Map()
|
|
10
|
+
this.reset()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
reset () {
|
|
14
|
+
this.position = 0
|
|
15
|
+
this.depth = 0
|
|
16
|
+
this.neededLength = 0
|
|
17
|
+
this.postBundlePosition = 0
|
|
18
|
+
this.headers = 0
|
|
19
|
+
this.mapsAsObjects = !!this.decoder.mapsAsObjects
|
|
20
|
+
this.restoreMap = false
|
|
21
|
+
this.records.clear()
|
|
22
|
+
this.readers.clear()
|
|
23
|
+
this.push('items', 1)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
push (kind, remaining) {
|
|
27
|
+
if (this.depth === 16384) throw new RangeError('Maximum CBOR nesting depth exceeded')
|
|
28
|
+
const frame = this.frames[this.depth] || (this.frames[this.depth] = {})
|
|
29
|
+
frame.kind = kind
|
|
30
|
+
frame.remaining = remaining
|
|
31
|
+
frame.stage = 0
|
|
32
|
+
frame.id = undefined
|
|
33
|
+
frame.restoreMap = false
|
|
34
|
+
frame.legacy = false
|
|
35
|
+
frame.definition = undefined
|
|
36
|
+
frame.limitMajor = 0
|
|
37
|
+
frame.count = 0
|
|
38
|
+
this.depth++
|
|
39
|
+
return frame
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
header (source, end, lengthOnly = false) {
|
|
43
|
+
const start = this.position
|
|
44
|
+
if (start >= end) {
|
|
45
|
+
this.neededLength = start + 1
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
const byte = source[start]
|
|
49
|
+
const major = byte >> 5
|
|
50
|
+
const info = byte & 31
|
|
51
|
+
const width = info < 24 || (lengthOnly && info > 26) ? 0 : info === 24 ? 1 : info === 25 ? 2 : info === 26 ? 4 : info === 27 ? 8 : 0
|
|
52
|
+
if (!lengthOnly && info >= 28 && info <= 30) throw new Error('Unknown token ' + info)
|
|
53
|
+
if (start + 1 + width > end) {
|
|
54
|
+
this.neededLength = start + 1 + width
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
let value = info
|
|
58
|
+
if (width) {
|
|
59
|
+
value = 0
|
|
60
|
+
for (let i = start + 1; i <= start + width; i++) value = value * 256 + source[i]
|
|
61
|
+
if (width === 8 && major > 1 && major !== 7 && value > 0xffffffff) {
|
|
62
|
+
throw new Error('JavaScript does not support arrays, maps, or strings with length over 4294967295')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.major = major
|
|
66
|
+
this.info = info
|
|
67
|
+
this.argument = value
|
|
68
|
+
this.position = start + 1 + width
|
|
69
|
+
this.headers++
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
scan (source, end) {
|
|
74
|
+
while (this.depth) {
|
|
75
|
+
if (this.position > end) {
|
|
76
|
+
this.neededLength = this.position
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
const frame = this.frames[this.depth - 1]
|
|
80
|
+
if (frame.kind === 'bundle') {
|
|
81
|
+
if (frame.stage === 0) {
|
|
82
|
+
if (!this.header(source, end, true)) return false
|
|
83
|
+
frame.remaining = Math.max(0, this.argument - 2)
|
|
84
|
+
frame.stage = 1
|
|
85
|
+
}
|
|
86
|
+
if (frame.stage === 1) {
|
|
87
|
+
const offsetStart = this.position
|
|
88
|
+
if (!this.header(source, end)) return false
|
|
89
|
+
if (this.major > 1 || this.info === 31) throw new Error('Invalid bundled string offset')
|
|
90
|
+
frame.target = offsetStart + (this.major === 1 ? -1 - this.argument : this.argument)
|
|
91
|
+
if (!Number.isSafeInteger(frame.target) || frame.target < 0) throw new Error('Invalid bundled string offset')
|
|
92
|
+
frame.stage = 2
|
|
93
|
+
}
|
|
94
|
+
if (frame.stage === 2) {
|
|
95
|
+
if (frame.remaining) {
|
|
96
|
+
if (!this.header(source, end, true)) return false
|
|
97
|
+
this.position += this.argument
|
|
98
|
+
frame.remaining--
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
frame.resume = this.position
|
|
102
|
+
this.position = frame.target
|
|
103
|
+
frame.remaining = 2
|
|
104
|
+
frame.stage = 3
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (frame.stage === 3) {
|
|
108
|
+
if (frame.remaining) {
|
|
109
|
+
if (!this.header(source, end, true)) return false
|
|
110
|
+
this.position += this.argument
|
|
111
|
+
frame.remaining--
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
this.postBundlePosition = this.position
|
|
115
|
+
this.position = frame.resume
|
|
116
|
+
frame.kind = 'items'
|
|
117
|
+
frame.remaining = 1
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (frame.kind === 'record') {
|
|
122
|
+
if (!this.header(source, end, true)) return false
|
|
123
|
+
if (this.info > 26) throw new Error('Expected array header, but got ' + source[this.position - 1])
|
|
124
|
+
frame.kind = 'items'
|
|
125
|
+
frame.remaining = this.argument
|
|
126
|
+
const reader = frame.reader
|
|
127
|
+
if (reader.compiled.has(this.argument)) frame.remaining = Math.min(this.argument, reader.length)
|
|
128
|
+
else if (reader.slowReads++ >= _stream.readThreshold) {
|
|
129
|
+
reader.compiled.add(this.argument)
|
|
130
|
+
frame.remaining = Math.min(this.argument, reader.length)
|
|
131
|
+
}
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
if (frame.kind === 'definition' && frame.stage === 0) {
|
|
135
|
+
const start = this.position
|
|
136
|
+
if (!this.header(source, end, !frame.legacy)) return false
|
|
137
|
+
if (frame.legacy && this.major !== 4) {
|
|
138
|
+
this.position = start
|
|
139
|
+
this.headers--
|
|
140
|
+
frame.kind = 'items'
|
|
141
|
+
frame.remaining = 1
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
frame.remaining = frame.legacy ? (this.info === 31 ? -1 : this.argument) : Math.max(2, this.argument)
|
|
145
|
+
if (frame.legacy) {
|
|
146
|
+
if (frame.remaining >= 0) _stream.checkContainer(4, frame.remaining)
|
|
147
|
+
else frame.limitMajor = 4
|
|
148
|
+
frame.index = 0
|
|
149
|
+
}
|
|
150
|
+
frame.total = frame.remaining
|
|
151
|
+
frame.stage = 1
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
if (frame.remaining === 0) {
|
|
155
|
+
if (frame.definition) this.records.set(frame.id - 0xe000, frame.definition)
|
|
156
|
+
if (frame.restoreMap && this.restoreMap) {
|
|
157
|
+
this.mapsAsObjects = true
|
|
158
|
+
this.restoreMap = false
|
|
159
|
+
}
|
|
160
|
+
this.depth--
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
if (!this.header(source, end)) return false
|
|
164
|
+
let { major, argument, info } = this
|
|
165
|
+
if (frame.kind === 'definition') {
|
|
166
|
+
const index = frame.legacy ? frame.index++ : frame.total - frame.remaining
|
|
167
|
+
if (index === 0 && major === 0) frame.id = argument
|
|
168
|
+
if ((frame.multiple ? index >= 1 && index < frame.total - 1 : index === 1) && major === 4 && info !== 31 && frame.id !== undefined) {
|
|
169
|
+
const definition = { length: argument, slowReads: 0, compiled: new Set() }
|
|
170
|
+
if (frame.legacy) frame.definition = definition
|
|
171
|
+
else this.records.set(frame.id - 0xe000 + (frame.multiple ? index - 1 : 0), definition)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (major === 7 && info === 31 && (frame.remaining === -1 || frame.remaining === -2)) {
|
|
175
|
+
if (frame.definition) this.records.set(frame.id - 0xe000, frame.definition)
|
|
176
|
+
this.depth--
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
if (frame.limitMajor && (frame.remaining === -1 || frame.remaining === -2)) {
|
|
180
|
+
_stream.checkContainer(frame.limitMajor, ++frame.count, frame.mapsAsObjects)
|
|
181
|
+
}
|
|
182
|
+
if (frame.remaining > 0) frame.remaining--
|
|
183
|
+
else if (frame.remaining === -2) frame.remaining = -3
|
|
184
|
+
else if (frame.remaining === -3) frame.remaining = -2
|
|
185
|
+
if (info === 31) {
|
|
186
|
+
if (major === 2 || major === 3) throw new Error('Indefinite length not supported for byte or text strings')
|
|
187
|
+
if (major === 4 || major === 5) {
|
|
188
|
+
const container = this.push('items', major === 4 ? -1 : -2)
|
|
189
|
+
container.limitMajor = major
|
|
190
|
+
container.mapsAsObjects = this.mapsAsObjects
|
|
191
|
+
if (major === 5 && this.restoreMap) {
|
|
192
|
+
this.mapsAsObjects = true
|
|
193
|
+
this.restoreMap = false
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (major === 7) throw new Error('Unexpected break stop code outside of an indefinite-length item')
|
|
197
|
+
else throw new Error('Invalid major type for indefinite length ' + major)
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
switch (major) {
|
|
201
|
+
case 2:
|
|
202
|
+
case 3:
|
|
203
|
+
this.position += argument
|
|
204
|
+
break
|
|
205
|
+
case 4:
|
|
206
|
+
case 5:
|
|
207
|
+
_stream.checkContainer(major, argument, this.mapsAsObjects)
|
|
208
|
+
if (major === 5 && this.restoreMap) {
|
|
209
|
+
this.mapsAsObjects = true
|
|
210
|
+
this.restoreMap = false
|
|
211
|
+
}
|
|
212
|
+
if (argument) this.push('items', major === 5 ? argument * 2 : argument)
|
|
213
|
+
break
|
|
214
|
+
case 6: {
|
|
215
|
+
const structure = _stream.getTag(argument, this.decoder)
|
|
216
|
+
let reader = this.records.get(argument & 0x1fff)
|
|
217
|
+
if (argument >= 0xdff9 && (structure || reader)) {
|
|
218
|
+
if (!reader) {
|
|
219
|
+
reader = this.readers.get(structure)
|
|
220
|
+
if (!reader) {
|
|
221
|
+
reader = { length: structure.length, slowReads: structure.slowReads || 0, compiled: new Set() }
|
|
222
|
+
for (let compiled = structure.compiledReader; compiled; compiled = compiled.next) reader.compiled.add(compiled.propertyCount)
|
|
223
|
+
this.readers.set(structure, reader)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
this.push('record').reader = reader
|
|
227
|
+
} else if (argument === 0xdff9) {
|
|
228
|
+
this.push('bundle')
|
|
229
|
+
} else if (argument === 0xdfff || argument === 0xdffe) {
|
|
230
|
+
this.push('definition').multiple = argument === 0xdffe
|
|
231
|
+
} else if (argument === 259 && _stream.isBuiltinTag(argument) && this.mapsAsObjects) {
|
|
232
|
+
this.push('items', 1).restoreMap = true
|
|
233
|
+
this.mapsAsObjects = false
|
|
234
|
+
this.restoreMap = true
|
|
235
|
+
} else if (argument === 105 && _stream.isBuiltinTag(argument)) {
|
|
236
|
+
const definition = this.push('definition')
|
|
237
|
+
definition.multiple = false
|
|
238
|
+
definition.legacy = true
|
|
239
|
+
} else {
|
|
240
|
+
this.push('items', 1)
|
|
241
|
+
}
|
|
242
|
+
break
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (this.postBundlePosition) {
|
|
247
|
+
if (this.position >= this.postBundlePosition) throw new Error('Unexpected bundle position')
|
|
248
|
+
this.position = this.postBundlePosition
|
|
249
|
+
}
|
|
250
|
+
if (this.frames.length > 256) this.frames.length = 256
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
}
|
package/stream.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Transform } from 'stream'
|
|
2
2
|
import { Encoder } from './encode.js'
|
|
3
3
|
import { checkedRead, getPosition, Decoder, clearSource } from './decode.js'
|
|
4
|
+
import { ChunkDecoder } from './iterators.js'
|
|
4
5
|
var DEFAULT_OPTIONS = {objectMode: true}
|
|
5
6
|
|
|
6
7
|
export class EncoderStream extends Transform {
|
|
@@ -30,21 +31,16 @@ export class DecoderStream extends Transform {
|
|
|
30
31
|
super(options)
|
|
31
32
|
options.structures = []
|
|
32
33
|
this.decoder = options.decoder || new Decoder(options)
|
|
34
|
+
this.chunkDecoder = new ChunkDecoder(this.decoder, options.maxIncompleteBufferSize)
|
|
33
35
|
}
|
|
34
36
|
_transform(chunk, encoding, callback) {
|
|
35
|
-
if (this.incompleteBuffer) {
|
|
36
|
-
chunk = Buffer.concat([this.incompleteBuffer, chunk])
|
|
37
|
-
this.incompleteBuffer = null
|
|
38
|
-
}
|
|
39
37
|
let values, fatalError
|
|
40
38
|
try {
|
|
41
|
-
|
|
39
|
+
// decodeMultiple is called directly, since another call level here measurably slows small writes
|
|
40
|
+
values = this.chunkDecoder.buffer ? this.chunkDecoder.decodeAfterIncomplete(chunk) : this.decoder.decodeMultiple(chunk)
|
|
42
41
|
} catch(error) {
|
|
43
|
-
values = error.values
|
|
44
|
-
|
|
45
|
-
this.incompleteBuffer = chunk.slice(error.lastPosition)
|
|
46
|
-
else
|
|
47
|
-
fatalError = error
|
|
42
|
+
values = error && error.values
|
|
43
|
+
fatalError = this.chunkDecoder.keepIncomplete(error, chunk)
|
|
48
44
|
}
|
|
49
45
|
for (let value of values || []) {
|
|
50
46
|
if (value === null)
|
|
@@ -53,6 +49,9 @@ export class DecoderStream extends Transform {
|
|
|
53
49
|
}
|
|
54
50
|
callback(fatalError)
|
|
55
51
|
}
|
|
52
|
+
_flush(callback) {
|
|
53
|
+
callback(this.chunkDecoder.end())
|
|
54
|
+
}
|
|
56
55
|
getNullValue() {
|
|
57
56
|
return Symbol.for(null)
|
|
58
57
|
}
|