msgpack5 6.0.2 → 6.1.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.
package/example.js CHANGED
@@ -8,8 +8,13 @@ const decode = msgpack.decode
8
8
 
9
9
  msgpack.register(0x42, MyType, mytipeEncode, mytipeDecode)
10
10
 
11
- console.log(encode({ hello: 'world' }).toString('hex'))
11
+ const hex = encode({ hello: 'world' }).toString('hex')
12
+ console.log(hex)
12
13
  // 81a568656c6c6fa5776f726c64
14
+ const obj = decode(Buffer.from(hex, 'hex'))
15
+ console.log(obj)
16
+ // { hello: 'world' }
17
+
13
18
  console.log(decode(encode({ hello: 'world' })))
14
19
  // { hello: 'world' }
15
20
  console.log(encode(a).toString('hex'))
package/index.js CHANGED
@@ -13,17 +13,25 @@ function msgpack (options) {
13
13
  const encodingTypes = []
14
14
  const decodingTypes = new Map()
15
15
 
16
- options = options || {
16
+ options = Object.assign({
17
17
  forceFloat64: false,
18
18
  compatibilityMode: false,
19
19
  // if true, skips encoding Dates using the msgpack
20
20
  // timestamp ext format (-1)
21
21
  disableTimestampEncoding: false,
22
22
  preferMap: false,
23
- // options.protoAction: 'error' (default) / 'remove' / 'ignore'
23
+ maxDepth: 100,
24
24
  protoAction: 'error'
25
+ }, options || {})
26
+
27
+ if (options.protoAction === undefined) options.protoAction = 'error'
28
+ if (options.protoAction !== 'error' && options.protoAction !== 'remove' && options.protoAction !== 'ignore') {
29
+ throw new TypeError('protoAction must be "error", "remove", or "ignore"')
25
30
  }
26
31
 
32
+ validateMaxLength(options.maxArrayLength, 'maxArrayLength')
33
+ validateMaxLength(options.maxMapLength, 'maxMapLength')
34
+
27
35
  decodingTypes.set(DateCodec.type, DateCodec.decode)
28
36
  if (!options.disableTimestampEncoding) {
29
37
  encodingTypes.push(DateCodec)
@@ -88,4 +96,10 @@ function msgpack (options) {
88
96
  }
89
97
  }
90
98
 
99
+ function validateMaxLength (value, name) {
100
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
101
+ throw new TypeError(name + ' must be a non-negative safe integer')
102
+ }
103
+ }
104
+
91
105
  module.exports = msgpack
package/lib/decoder.js CHANGED
@@ -28,9 +28,10 @@ const SIZES = {
28
28
  0xd9: 2,
29
29
  0xda: 3,
30
30
  0xdb: 5,
31
- 0xde: 3,
32
31
  0xdc: 3,
33
- 0xdd: 5
32
+ 0xdd: 5,
33
+ 0xde: 3,
34
+ 0xdf: 5
34
35
  }
35
36
 
36
37
  function isValidDataSize (dataLength, bufLength, headerLength) {
@@ -38,30 +39,135 @@ function isValidDataSize (dataLength, bufLength, headerLength) {
38
39
  }
39
40
 
40
41
  module.exports = function buildDecode (decodingTypes, options) {
41
- const context = { decodingTypes, options, decode }
42
+ const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth
43
+ if (!Number.isInteger(maxDepth) || maxDepth < 0) {
44
+ throw new TypeError('maxDepth must be a non-negative integer')
45
+ }
46
+
47
+ const context = { decodingTypes, options, maxDepth, decode }
42
48
  return decode
43
49
 
44
- function decode (buf) {
50
+ function decode (buf, decodeState) {
45
51
  if (!bl.isBufferList(buf)) {
46
52
  buf = bl(buf)
47
53
  }
48
54
 
49
- const result = tryDecode(buf, 0, context)
55
+ let result
56
+ try {
57
+ result = decodeState
58
+ ? tryDecodeIncremental(buf, decodeState, context)
59
+ : tryDecode(buf, 0, context, 0)
60
+ } catch (err) {
61
+ if (decodeState) decodeState.stack.length = 0
62
+ throw err
63
+ }
64
+
50
65
  // Handle worst case ASAP and keep code flat
51
66
  if (!result) throw new IncompleteBufferError()
52
67
 
53
- buf.consume(result[1])
68
+ if (!decodeState) buf.consume(result[1])
54
69
  return result[0]
55
70
  }
56
71
  }
57
72
 
58
- function decodeArray (buf, initialOffset, length, headerLength, context) {
73
+ function tryDecodeIncremental (buf, state, context) {
74
+ while (buf.length > 0) {
75
+ const container = decodeContainerHeader(buf)
76
+ let value
77
+
78
+ if (container === null) return null
79
+
80
+ if (container) {
81
+ const depth = state.stack.length + 1
82
+ if (depth > context.maxDepth) {
83
+ throw new Error('Maximum decode depth exceeded')
84
+ }
85
+ checkCollectionLength(
86
+ container.type,
87
+ container.length,
88
+ container.type === 'map' ? context.options.maxMapLength : context.options.maxArrayLength
89
+ )
90
+
91
+ buf.consume(container.headerLength)
92
+
93
+ const itemCount = container.type === 'map'
94
+ ? 2 * container.length
95
+ : container.length
96
+
97
+ if (itemCount > 0) {
98
+ state.stack.push({
99
+ type: container.type,
100
+ length: container.length,
101
+ itemCount,
102
+ result: []
103
+ })
104
+ continue
105
+ }
106
+
107
+ value = container.type === 'map'
108
+ ? buildMap([], 0, context)
109
+ : []
110
+ } else {
111
+ const result = tryDecode(buf, 0, context, state.stack.length)
112
+ if (!result) return null
113
+
114
+ buf.consume(result[1])
115
+ value = result[0]
116
+ }
117
+
118
+ const completed = completeIncrementalValue(state, value, context)
119
+ if (completed) return completed
120
+ }
121
+
122
+ return null
123
+ }
124
+
125
+ function completeIncrementalValue (state, value, context) {
126
+ while (state.stack.length > 0) {
127
+ const frame = state.stack[state.stack.length - 1]
128
+ frame.result.push(value)
129
+
130
+ if (frame.result.length < frame.itemCount) return null
131
+
132
+ state.stack.pop()
133
+ value = frame.type === 'map'
134
+ ? buildMap(frame.result, frame.length, context)
135
+ : frame.result
136
+ }
137
+
138
+ return [value]
139
+ }
140
+
141
+ function decodeContainerHeader (buf) {
142
+ const first = buf.readUInt8(0)
143
+
144
+ if ((first & 0xf0) === 0x80) {
145
+ return { type: 'map', length: first & 0x0f, headerLength: 1 }
146
+ }
147
+ if ((first & 0xf0) === 0x90) {
148
+ return { type: 'array', length: first & 0x0f, headerLength: 1 }
149
+ }
150
+
151
+ let headerLength
152
+ if (first === 0xdc || first === 0xde) headerLength = 3
153
+ if (first === 0xdd || first === 0xdf) headerLength = 5
154
+ if (!headerLength) return false
155
+ if (buf.length < headerLength) return null
156
+
157
+ return {
158
+ type: first === 0xdc || first === 0xdd ? 'array' : 'map',
159
+ length: buf.readUIntBE(1, headerLength - 1),
160
+ headerLength
161
+ }
162
+ }
163
+
164
+ function decodeItems (buf, initialOffset, length, headerLength, context, depth) {
59
165
  let offset = initialOffset
60
166
  const result = []
61
167
  let i = 0
62
168
 
63
169
  while (i++ < length) {
64
- const decodeResult = tryDecode(buf, offset, context)
170
+ const decodeResult = tryDecode(buf, offset, context, depth)
65
171
  if (!decodeResult) return null
66
172
 
67
173
  result.push(decodeResult[0])
@@ -70,11 +176,32 @@ function decodeArray (buf, initialOffset, length, headerLength, context) {
70
176
  return [result, headerLength + offset - initialOffset]
71
177
  }
72
178
 
73
- function decodeMap (buf, offset, length, headerLength, context) {
74
- const _temp = decodeArray(buf, offset, 2 * length, headerLength, context)
179
+ function checkCollectionLength (type, length, maxLength) {
180
+ if (maxLength !== undefined && length > maxLength) {
181
+ throw new RangeError(type + ' length ' + length + ' exceeds configured limit of ' + maxLength)
182
+ }
183
+ }
184
+
185
+ function decodeArray (buf, initialOffset, length, headerLength, context, depth) {
186
+ if (depth > context.maxDepth) {
187
+ throw new Error('Maximum decode depth exceeded')
188
+ }
189
+ checkCollectionLength('array', length, context.options.maxArrayLength)
190
+ return decodeItems(buf, initialOffset, length, headerLength, context, depth)
191
+ }
192
+
193
+ function decodeMap (buf, offset, length, headerLength, context, depth) {
194
+ if (depth > context.maxDepth) {
195
+ throw new Error('Maximum decode depth exceeded')
196
+ }
197
+ checkCollectionLength('map', length, context.options.maxMapLength)
198
+ const _temp = decodeItems(buf, offset, 2 * length, headerLength, context, depth)
75
199
  if (!_temp) return null
76
200
  const [result, consumedBytes] = _temp
201
+ return [buildMap(result, length, context), consumedBytes]
202
+ }
77
203
 
204
+ function buildMap (result, length, context) {
78
205
  let isPlainObject = !context.options.preferMap
79
206
 
80
207
  if (isPlainObject) {
@@ -104,7 +231,7 @@ function decodeMap (buf, offset, length, headerLength, context) {
104
231
 
105
232
  object[key] = val
106
233
  }
107
- return [object, consumedBytes]
234
+ return object
108
235
  } else {
109
236
  const mapping = new Map()
110
237
  for (let i = 0; i < 2 * length; i += 2) {
@@ -112,11 +239,11 @@ function decodeMap (buf, offset, length, headerLength, context) {
112
239
  const val = result[i + 1]
113
240
  mapping.set(key, val)
114
241
  }
115
- return [mapping, consumedBytes]
242
+ return mapping
116
243
  }
117
244
  }
118
245
 
119
- function tryDecode (buf, initialOffset, context) {
246
+ function tryDecode (buf, initialOffset, context, depth) {
120
247
  if (buf.length <= initialOffset) return null
121
248
 
122
249
  const bufLength = buf.length - initialOffset
@@ -133,13 +260,13 @@ function tryDecode (buf, initialOffset, context) {
133
260
  const length = first & 0x0f
134
261
  const headerSize = offset - initialOffset
135
262
  // we have a map with less than 15 elements
136
- return decodeMap(buf, offset, length, headerSize, context)
263
+ return decodeMap(buf, offset, length, headerSize, context, depth + 1)
137
264
  }
138
265
  if ((first & 0xf0) === 0x90) {
139
266
  const length = first & 0x0f
140
267
  const headerSize = offset - initialOffset
141
268
  // we have an array with less than 15 elements
142
- return decodeArray(buf, offset, length, headerSize, context)
269
+ return decodeArray(buf, offset, length, headerSize, context, depth + 1)
143
270
  }
144
271
 
145
272
  if ((first & 0xe0) === 0xa0) {
@@ -149,6 +276,7 @@ function tryDecode (buf, initialOffset, context) {
149
276
  const result = buf.toString('utf8', offset, offset + length)
150
277
  return [result, length + 1]
151
278
  }
279
+ if (first === 0xc1) throw new Error('0xc1 is a reserved MessagePack byte')
152
280
  if (first >= 0xc0 && first <= 0xc3) return decodeConstants(first)
153
281
  if (first >= 0xc4 && first <= 0xc6) {
154
282
  const length = buf.readUIntBE(offset, size - 1)
@@ -188,7 +316,7 @@ function tryDecode (buf, initialOffset, context) {
188
316
  if (first >= 0xdc && first <= 0xdd) {
189
317
  const length = buf.readUIntBE(offset, size - 1)
190
318
  offset += size - 1
191
- return decodeArray(buf, offset, length, size, context)
319
+ return decodeArray(buf, offset, length, size, context, depth + 1)
192
320
  }
193
321
  if (first >= 0xde && first <= 0xdf) {
194
322
  let length
@@ -198,12 +326,12 @@ function tryDecode (buf, initialOffset, context) {
198
326
  length = buf.readUInt16BE(offset)
199
327
  offset += 2
200
328
  // console.log(offset - initialOffset)
201
- return decodeMap(buf, offset, length, 3, context)
329
+ return decodeMap(buf, offset, length, 3, context, depth + 1)
202
330
 
203
331
  case 0xdf:
204
332
  length = buf.readUInt32BE(offset)
205
333
  offset += 4
206
- return decodeMap(buf, offset, length, 5, context)
334
+ return decodeMap(buf, offset, length, 5, context, depth + 1)
207
335
  }
208
336
  }
209
337
  if (first >= 0xe0) return [first - 0x100, 1] // 5 bits negative ints
@@ -216,7 +344,7 @@ function decodeSigned (buf, offset, size) {
216
344
  if (size === 1) result = buf.readInt8(offset)
217
345
  if (size === 2) result = buf.readInt16BE(offset)
218
346
  if (size === 4) result = buf.readInt32BE(offset)
219
- if (size === 8) result = readInt64BE(buf.slice(offset, offset + 8), 0)
347
+ if (size === 8) result = readInt64BE(buf, offset)
220
348
  return [result, size + 1]
221
349
  }
222
350
 
@@ -251,18 +379,14 @@ function decodeFloat (buf, offset, size) {
251
379
  }
252
380
 
253
381
  function readInt64BE (buf, offset) {
254
- var negate = (buf[offset] & 0x80) == 0x80; // eslint-disable-line
382
+ const negate = (buf.readUInt8(offset) & 0x80) === 0x80
383
+ let hi = buf.readUInt32BE(offset + 0)
384
+ let lo = buf.readUInt32BE(offset + 4)
255
385
 
256
386
  if (negate) {
257
- let carry = 1
258
- for (let i = offset + 7; i >= offset; i--) {
259
- const v = (buf[i] ^ 0xff) + carry
260
- buf[i] = v & 0xff
261
- carry = v >> 8
262
- }
387
+ lo = (~lo + 1) >>> 0
388
+ hi = (~hi + (lo === 0 ? 1 : 0)) >>> 0
263
389
  }
264
390
 
265
- const hi = buf.readUInt32BE(offset + 0)
266
- const lo = buf.readUInt32BE(offset + 4)
267
391
  return (hi * 4294967296 + lo) * (negate ? -1 : +1)
268
392
  }
package/lib/streams.js CHANGED
@@ -54,6 +54,7 @@ function Decoder (opts) {
54
54
  Base.call(this, opts)
55
55
 
56
56
  this._chunks = bl()
57
+ this._decodeState = { stack: [] }
57
58
  this._wrap = ('wrap' in opts) && opts.wrap
58
59
  }
59
60
 
@@ -64,26 +65,26 @@ Decoder.prototype._transform = function (buf, enc, done) {
64
65
  this._chunks.append(buf)
65
66
  }
66
67
 
67
- try {
68
- let result = this._msgpack.decode(this._chunks)
69
- if (this._wrap) {
70
- result = { value: result }
71
- }
72
- this.push(result)
73
- } catch (err) {
74
- if (err instanceof this._msgpack.IncompleteBufferError) {
75
- done()
76
- } else {
77
- this.emit('error', err)
68
+ while (this._chunks.length > 0) {
69
+ try {
70
+ let result = this._msgpack.decode(this._chunks, this._decodeState)
71
+ if (this._wrap) {
72
+ result = { value: result }
73
+ }
74
+ this.push(result)
75
+ } catch (err) {
76
+ if (err instanceof this._msgpack.IncompleteBufferError) {
77
+ done()
78
+ } else {
79
+ this._chunks = bl()
80
+ this.destroy(err)
81
+ done()
82
+ }
83
+ return
78
84
  }
79
- return
80
85
  }
81
86
 
82
- if (this._chunks.length > 0) {
83
- this._transform(null, enc, done)
84
- } else {
85
- done()
86
- }
87
+ done()
87
88
  }
88
89
 
89
90
  module.exports.decoder = Decoder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msgpack5",
3
- "version": "6.0.2",
3
+ "version": "6.1.0",
4
4
  "description": "A msgpack v5 implementation for node.js and the browser, with extension points",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const Buffer = require('safe-buffer').Buffer
4
+ const test = require('tape').test
5
+ const msgpack = require('../')
6
+ const bl = require('bl')
7
+
8
+ test('decoding incomplete map32 headers', function (t) {
9
+ const pack = msgpack()
10
+
11
+ for (let length = 1; length < 5; length++) {
12
+ const buf = Buffer.alloc(length)
13
+ buf[0] = 0xdf
14
+ const input = bl().append(buf)
15
+
16
+ t.throws(function () {
17
+ pack.decode(input)
18
+ }, pack.IncompleteBufferError, 'must reject a ' + length + '-byte header as incomplete')
19
+ t.equal(input.length, length, 'must not consume an incomplete header')
20
+ }
21
+
22
+ t.end()
23
+ })
24
+
25
+ test('decoding an empty map32', function (t) {
26
+ const pack = msgpack()
27
+ const buf = Buffer.from([0xdf, 0x00, 0x00, 0x00, 0x00])
28
+
29
+ t.deepEqual(pack.decode(buf), {}, 'must decode a complete map32 header')
30
+ t.end()
31
+ })
@@ -34,6 +34,27 @@ test('encoding/decoding 64-bits big-endian signed integers', function (t) {
34
34
  t.end()
35
35
  })
36
36
 
37
+ test('decoding a negative 64-bits integer does not mutate the input', function (t) {
38
+ const encoder = msgpack()
39
+ const encoded = Buffer.from([0xd3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe])
40
+ const expected = encoded.toString('hex')
41
+
42
+ t.equal(encoder.decode(encoded), -2, 'decodes a direct buffer')
43
+ t.equal(encoded.toString('hex'), expected, 'does not mutate a direct buffer')
44
+ t.equal(encoder.decode(encoded), -2, 'decodes the same buffer consistently')
45
+ t.equal(encoded.toString('hex'), expected, 'leaves the buffer unchanged after repeated decoding')
46
+
47
+ const singleChunk = Buffer.from(encoded)
48
+ t.equal(encoder.decode(bl().append(singleChunk)), -2, 'decodes a single-chunk BufferList')
49
+ t.equal(singleChunk.toString('hex'), expected, 'does not mutate the underlying chunk')
50
+
51
+ const firstChunk = Buffer.from(encoded.slice(0, 4))
52
+ const secondChunk = Buffer.from(encoded.slice(4))
53
+ t.equal(encoder.decode(bl().append(firstChunk).append(secondChunk)), -2, 'decodes a split-chunk BufferList')
54
+ t.equal(Buffer.concat([firstChunk, secondChunk]).toString('hex'), expected, 'does not mutate split chunks')
55
+ t.end()
56
+ })
57
+
37
58
  test('decoding an incomplete 64-bits big-endian signed integer', function (t) {
38
59
  const encoder = msgpack()
39
60
  let buf = Buffer.allocUnsafe(8)
@@ -0,0 +1,64 @@
1
+ 'use strict'
2
+
3
+ const Buffer = require('safe-buffer').Buffer
4
+ const test = require('tape').test
5
+ const msgpack = require('../')
6
+
7
+ test('maxArrayLength limits decoded arrays', function (t) {
8
+ const decoder = msgpack({ maxArrayLength: 2 })
9
+
10
+ t.deepEqual(decoder.decode(Buffer.from([0x92, 0x01, 0x02])), [1, 2], 'allows the configured limit')
11
+ t.throws(function () {
12
+ decoder.decode(Buffer.from([0x93, 0x01, 0x02, 0x03]))
13
+ }, /array length 3 exceeds configured limit of 2/, 'rejects fixarray over the limit')
14
+ t.throws(function () {
15
+ decoder.decode(Buffer.from([0xdc, 0x00, 0x03]))
16
+ }, /array length 3 exceeds configured limit of 2/, 'rejects array16 before decoding its elements')
17
+ t.throws(function () {
18
+ decoder.decode(Buffer.from([0xdd, 0x00, 0x00, 0x00, 0x03]))
19
+ }, /array length 3 exceeds configured limit of 2/, 'rejects array32 before decoding its elements')
20
+ t.end()
21
+ })
22
+
23
+ test('maxMapLength limits decoded maps', function (t) {
24
+ const decoder = msgpack({ maxMapLength: 1 })
25
+
26
+ t.deepEqual(decoder.decode(Buffer.from([0x81, 0xa1, 0x61, 0x01])), { a: 1 }, 'allows the configured limit')
27
+ t.throws(function () {
28
+ decoder.decode(Buffer.from([0x82, 0xa1, 0x61, 0x01, 0xa1, 0x62, 0x02]))
29
+ }, /map length 2 exceeds configured limit of 1/, 'rejects fixmap over the limit')
30
+ t.throws(function () {
31
+ decoder.decode(Buffer.from([0xde, 0x00, 0x02]))
32
+ }, /map length 2 exceeds configured limit of 1/, 'rejects map16 before decoding its entries')
33
+ t.throws(function () {
34
+ decoder.decode(Buffer.from([0xdf, 0x00, 0x00, 0x00, 0x02]))
35
+ }, /map length 2 exceeds configured limit of 1/, 'rejects map32 before decoding its entries')
36
+ t.end()
37
+ })
38
+
39
+ test('collection limits apply independently and to nested values', function (t) {
40
+ const decoder = msgpack({ maxArrayLength: 1, maxMapLength: 1 })
41
+
42
+ t.deepEqual(decoder.decode(Buffer.from([0x81, 0xa1, 0x61, 0x91, 0x01])), { a: [1] }, 'allows nested collections at their limits')
43
+ t.throws(function () {
44
+ decoder.decode(Buffer.from([0x81, 0xa1, 0x61, 0x92, 0x01, 0x02]))
45
+ }, /array length 2 exceeds configured limit of 1/, 'rejects a nested array over its limit')
46
+ t.end()
47
+ })
48
+
49
+ test('collection limits must be non-negative safe integers', function (t) {
50
+ const invalid = [-1, 1.5, Infinity, null, '1']
51
+
52
+ invalid.forEach(function (value) {
53
+ t.throws(function () {
54
+ msgpack({ maxArrayLength: value })
55
+ }, /maxArrayLength must be a non-negative safe integer/, 'rejects maxArrayLength ' + value)
56
+ t.throws(function () {
57
+ msgpack({ maxMapLength: value })
58
+ }, /maxMapLength must be a non-negative safe integer/, 'rejects maxMapLength ' + value)
59
+ })
60
+ t.doesNotThrow(function () {
61
+ msgpack({ maxArrayLength: 0, maxMapLength: Number.MAX_SAFE_INTEGER })
62
+ }, 'accepts boundary values')
63
+ t.end()
64
+ })
@@ -0,0 +1,95 @@
1
+ 'use strict'
2
+
3
+ const Buffer = require('safe-buffer').Buffer
4
+ const test = require('tape').test
5
+ const msgpack = require('../')
6
+
7
+ function nestedArray (depth) {
8
+ return Buffer.concat([Buffer.alloc(depth, 0x91), Buffer.from([0xc0])])
9
+ }
10
+
11
+ function nestedMap (depth) {
12
+ const prefix = Buffer.alloc(depth * 3)
13
+
14
+ for (let i = 0; i < depth; i++) {
15
+ prefix[i * 3] = 0x81
16
+ prefix[i * 3 + 1] = 0xa1
17
+ prefix[i * 3 + 2] = 0x78
18
+ }
19
+
20
+ return Buffer.concat([prefix, Buffer.from([0xc0])])
21
+ }
22
+
23
+ test('limits array and map nesting depth by default', function (t) {
24
+ const pack = msgpack()
25
+ let array = pack.decode(nestedArray(100))
26
+ let map = pack.decode(nestedMap(100))
27
+
28
+ for (let i = 0; i < 100; i++) {
29
+ array = array[0]
30
+ map = map.x
31
+ }
32
+
33
+ t.equal(array, null, 'decodes arrays at the limit')
34
+ t.equal(map, null, 'decodes maps at the limit')
35
+ t.throws(function () {
36
+ pack.decode(nestedArray(101))
37
+ }, /Maximum decode depth exceeded/, 'rejects arrays over the limit')
38
+ t.throws(function () {
39
+ pack.decode(nestedMap(101))
40
+ }, /Maximum decode depth exceeded/, 'rejects maps over the limit')
41
+ t.end()
42
+ })
43
+
44
+ test('supports a custom maximum nesting depth', function (t) {
45
+ const pack = msgpack({ maxDepth: 2 })
46
+
47
+ t.doesNotThrow(function () {
48
+ pack.decode(nestedArray(2))
49
+ }, 'decodes input at the configured limit')
50
+ t.throws(function () {
51
+ pack.decode(nestedArray(3))
52
+ }, /Maximum decode depth exceeded/, 'rejects input over the configured limit')
53
+ t.end()
54
+ })
55
+
56
+ test('defaults maxDepth when other options are provided', function (t) {
57
+ const pack = msgpack({ forceFloat64: true })
58
+
59
+ t.throws(function () {
60
+ pack.decode(nestedArray(101))
61
+ }, /Maximum decode depth exceeded/)
62
+ t.end()
63
+ })
64
+
65
+ test('allows scalars but no containers when maxDepth is zero', function (t) {
66
+ const pack = msgpack({ maxDepth: 0 })
67
+
68
+ t.equal(pack.decode(Buffer.from([0xc0])), null, 'decodes a scalar')
69
+ t.throws(function () {
70
+ pack.decode(Buffer.from([0x90]))
71
+ }, /Maximum decode depth exceeded/, 'rejects an empty container')
72
+ t.end()
73
+ })
74
+
75
+ test('validates maxDepth', function (t) {
76
+ const invalid = [-1, 1.5, Infinity, NaN, '100', null]
77
+
78
+ invalid.forEach(function (maxDepth) {
79
+ t.throws(function () {
80
+ msgpack({ maxDepth })
81
+ }, /maxDepth must be a non-negative integer/)
82
+ })
83
+ t.end()
84
+ })
85
+
86
+ test('reports a controlled error from the decoder stream', function (t) {
87
+ t.plan(2)
88
+
89
+ const decoder = msgpack().decoder()
90
+ decoder.on('error', function (err) {
91
+ t.equal(err.message, 'Maximum decode depth exceeded')
92
+ t.notOk(err instanceof RangeError, 'does not exhaust the native stack')
93
+ })
94
+ decoder.end(nestedArray(101))
95
+ })