compact-encoding 2.6.1 → 2.7.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.
Files changed (5) hide show
  1. package/README.md +12 -1
  2. package/index.js +84 -26
  3. package/lexint.js +114 -0
  4. package/package.json +1 -1
  5. package/test.js +158 -0
package/README.md CHANGED
@@ -79,13 +79,20 @@ to build others on top. Feel free to PR more that are missing.
79
79
  * `cenc.uint16` - Encodes a fixed size uint16. Useful for things like ports.
80
80
  * `cenc.uint24` - Encodes a fixed size uint24. Useful for message framing.
81
81
  * `cenc.uint32` - Encodes a fixed size uint32. Useful for very large message framing.
82
+ * `cenc.uint40` - Encodes a fixed size uint40.
83
+ * `cenc.uint48` - Encodes a fixed size uint48.
84
+ * `cenc.uint56` - Encodes a fixed size uint56.
82
85
  * `cenc.uint64` - Encodes a fixed size uint64.
83
86
  * `cenc.int` - Encodes an int using `cenc.uint` with ZigZag encoding.
84
87
  * `cenc.int8` - Encodes a fixed size int8 using `cenc.uint8` with ZigZag encoding.
85
88
  * `cenc.int16` - Encodes a fixed size int16 using `cenc.uint16` with ZigZag encoding.
86
89
  * `cenc.int24` - Encodes a fixed size int24 using `cenc.uint24` with ZigZag encoding.
87
90
  * `cenc.int32` - Encodes a fixed size int32 using `cenc.uint32` with ZigZag encoding.
91
+ * `cenc.int40` - Encodes a fixed size int40 using `cenc.uint40` with ZigZag encoding.
92
+ * `cenc.int48` - Encodes a fixed size int48 using `cenc.uint48` with ZigZag encoding.
93
+ * `cenc.int56` - Encodes a fixed size int56 using `cenc.uint56` with ZigZag encoding.
88
94
  * `cenc.int64` - Encodes a fixed size int64 using `cenc.uint64` with ZigZag encoding.
95
+ * `cenc.lexint` - Encodes an int using [lexicographic-integer](https://github.com/substack/lexicographic-integer) encoding so that encoded values are lexicographically sorted in ascending numerical order.
89
96
  * `cenc.float32` - Encodes a fixed size float32.
90
97
  * `cenc.float64` - Encodes a fixed size float64.
91
98
  * `cenc.buffer` - Encodes a buffer with its length uint prefixed. When decoding an empty buffer, `null` is returned.
@@ -99,7 +106,11 @@ to build others on top. Feel free to PR more that are missing.
99
106
  * `cenc.float32array` - Encodes a float32array with its element length uint prefixed.
100
107
  * `cenc.float64array` - Encodes a float64array with its element length uint prefixed.
101
108
  * `cenc.bool` - Encodes a boolean as 1 or 0.
102
- * `cenc.string` - Encodes a utf-8 string, similar to buffer.
109
+ * `cenc.string`, `cenc.utf8` - Encodes a utf-8 string, similar to buffer.
110
+ * `cenc.ascii` - Encodes an ascii string.
111
+ * `cenc.hex` - Encodes a hex string.
112
+ * `cenc.base64` - Encodes a base64 string.
113
+ * `cenc.utf16le`, `cenc.ucs2` - Encodes a utf16le string.
103
114
  * `cenc.fixed32` - Encodes a fixed 32 byte buffer.
104
115
  * `cenc.fixed64` - Encodes a fixed 64 byte buffer.
105
116
  * `cenc.fixed(n)` - Makes a fixed sized encoder.
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const b4a = require('b4a')
2
2
 
3
- const LE = (new Uint8Array(new Uint16Array([255]).buffer))[0] === 0xff
3
+ const LE = (new Uint8Array(new Uint16Array([0xff]).buffer))[0] === 0xff
4
4
  const BE = !LE
5
5
 
6
6
  exports.state = function () {
@@ -58,7 +58,7 @@ const uint16 = exports.uint16 = {
58
58
  if (state.end - state.start < 2) throw new Error('Out of bounds')
59
59
  return (
60
60
  state.buffer[state.start++] +
61
- state.buffer[state.start++] * 256
61
+ state.buffer[state.start++] * 0x100
62
62
  )
63
63
  }
64
64
  }
@@ -76,8 +76,8 @@ const uint24 = exports.uint24 = {
76
76
  if (state.end - state.start < 3) throw new Error('Out of bounds')
77
77
  return (
78
78
  state.buffer[state.start++] +
79
- state.buffer[state.start++] * 256 +
80
- state.buffer[state.start++] * 65536
79
+ state.buffer[state.start++] * 0x100 +
80
+ state.buffer[state.start++] * 0x10000
81
81
  )
82
82
  }
83
83
  }
@@ -96,25 +96,70 @@ const uint32 = exports.uint32 = {
96
96
  if (state.end - state.start < 4) throw new Error('Out of bounds')
97
97
  return (
98
98
  state.buffer[state.start++] +
99
- state.buffer[state.start++] * 256 +
100
- state.buffer[state.start++] * 65536 +
101
- state.buffer[state.start++] * 16777216
99
+ state.buffer[state.start++] * 0x100 +
100
+ state.buffer[state.start++] * 0x10000 +
101
+ state.buffer[state.start++] * 0x1000000
102
102
  )
103
103
  }
104
104
  }
105
105
 
106
+ const uint40 = exports.uint40 = {
107
+ preencode (state, n) {
108
+ state.end += 5
109
+ },
110
+ encode (state, n) {
111
+ const r = Math.floor(n / 0x100)
112
+ uint8.encode(state, n)
113
+ uint32.encode(state, r)
114
+ },
115
+ decode (state) {
116
+ if (state.end - state.start < 5) throw new Error('Out of bounds')
117
+ return uint8.decode(state) + 0x100 * uint32.decode(state)
118
+ }
119
+ }
120
+
121
+ const uint48 = exports.uint48 = {
122
+ preencode (state, n) {
123
+ state.end += 6
124
+ },
125
+ encode (state, n) {
126
+ const r = Math.floor(n / 0x10000)
127
+ uint16.encode(state, n)
128
+ uint32.encode(state, r)
129
+ },
130
+ decode (state) {
131
+ if (state.end - state.start < 6) throw new Error('Out of bounds')
132
+ return uint16.decode(state) + 0x10000 * uint32.decode(state)
133
+ }
134
+ }
135
+
136
+ const uint56 = exports.uint56 = {
137
+ preencode (state, n) {
138
+ state.end += 7
139
+ },
140
+ encode (state, n) {
141
+ const r = Math.floor(n / 0x1000000)
142
+ uint24.encode(state, n)
143
+ uint32.encode(state, r)
144
+ },
145
+ decode (state) {
146
+ if (state.end - state.start < 7) throw new Error('Out of bounds')
147
+ return uint24.decode(state) + 0x1000000 * uint32.decode(state)
148
+ }
149
+ }
150
+
106
151
  const uint64 = exports.uint64 = {
107
152
  preencode (state, n) {
108
153
  state.end += 8
109
154
  },
110
155
  encode (state, n) {
111
- const r = Math.floor(n / 4294967296)
156
+ const r = Math.floor(n / 0x100000000)
112
157
  uint32.encode(state, n)
113
158
  uint32.encode(state, r)
114
159
  },
115
160
  decode (state) {
116
161
  if (state.end - state.start < 8) throw new Error('Out of bounds')
117
- return uint32.decode(state) + 4294967296 * uint32.decode(state)
162
+ return uint32.decode(state) + 0x100000000 * uint32.decode(state)
118
163
  }
119
164
  }
120
165
 
@@ -123,8 +168,13 @@ exports.int8 = zigZag(uint8)
123
168
  exports.int16 = zigZag(uint16)
124
169
  exports.int24 = zigZag(uint24)
125
170
  exports.int32 = zigZag(uint32)
171
+ exports.int40 = zigZag(uint40)
172
+ exports.int48 = zigZag(uint48)
173
+ exports.int56 = zigZag(uint56)
126
174
  exports.int64 = zigZag(uint64)
127
175
 
176
+ exports.lexint = require('./lexint')
177
+
128
178
  exports.float32 = {
129
179
  preencode (state, n) {
130
180
  state.end += 4
@@ -236,25 +286,33 @@ exports.int32array = typedarray(Int32Array, b4a.swap32)
236
286
  exports.float32array = typedarray(Float32Array, b4a.swap32)
237
287
  exports.float64array = typedarray(Float64Array, b4a.swap64)
238
288
 
239
- exports.string = {
240
- preencode (state, s) {
241
- const len = b4a.byteLength(s)
242
- uint.preencode(state, len)
243
- state.end += len
244
- },
245
- encode (state, s) {
246
- const len = b4a.byteLength(s)
247
- uint.encode(state, len)
248
- b4a.write(state.buffer, s, state.start)
249
- state.start += len
250
- },
251
- decode (state) {
252
- const len = uint.decode(state)
253
- if (state.end - state.start < len) throw new Error('Out of bounds')
254
- return b4a.toString(state.buffer, 'utf-8', state.start, (state.start += len))
289
+ function string (encoding) {
290
+ return {
291
+ preencode (state, s) {
292
+ const len = b4a.byteLength(s, encoding)
293
+ uint.preencode(state, len)
294
+ state.end += len
295
+ },
296
+ encode (state, s) {
297
+ const len = b4a.byteLength(s, encoding)
298
+ uint.encode(state, len)
299
+ b4a.write(state.buffer, s, state.start, encoding)
300
+ state.start += len
301
+ },
302
+ decode (state) {
303
+ const len = uint.decode(state)
304
+ if (state.end - state.start < len) throw new Error('Out of bounds')
305
+ return b4a.toString(state.buffer, encoding, state.start, (state.start += len))
306
+ }
255
307
  }
256
308
  }
257
309
 
310
+ exports.string = exports.utf8 = string('utf-8')
311
+ exports.ascii = string('ascii')
312
+ exports.hex = string('hex')
313
+ exports.base64 = string('base64')
314
+ exports.ucs2 = exports.utf16le = string('utf16le')
315
+
258
316
  exports.bool = {
259
317
  preencode (state, b) {
260
318
  state.end++
@@ -311,7 +369,7 @@ exports.array = function array (enc) {
311
369
  },
312
370
  decode (state) {
313
371
  const len = uint.decode(state)
314
- if (len > 1048576) throw new Error('Array is too big')
372
+ if (len > 0x100000) throw new Error('Array is too big')
315
373
  const arr = new Array(len)
316
374
  for (let i = 0; i < len; i++) arr[i] = enc.decode(state)
317
375
  return arr
package/lexint.js ADDED
@@ -0,0 +1,114 @@
1
+ module.exports = {
2
+ preencode,
3
+ encode,
4
+ decode
5
+ }
6
+
7
+ function preencode (state, num) {
8
+ if (num < 251) {
9
+ state.end++
10
+ } else if (num < 256) {
11
+ state.end += 2
12
+ } else if (num < 0x10000) {
13
+ state.end += 3
14
+ } else if (num < 0x1000000) {
15
+ state.end += 4
16
+ } else if (num < 0x100000000) {
17
+ state.end += 5
18
+ } else {
19
+ state.end++
20
+ const exp = Math.floor(Math.log(num) / Math.log(2)) - 32
21
+ preencode(state, exp)
22
+ state.end += 6
23
+ }
24
+ }
25
+
26
+ function encode (state, num) {
27
+ const max = 251
28
+ const x = num - max
29
+
30
+ if (num < max) {
31
+ state.buffer[state.start++] = num
32
+ } else if (num < 256) {
33
+ state.buffer[state.start++] = max
34
+ state.buffer[state.start++] = x
35
+ } else if (num < 0x10000) {
36
+ state.buffer[state.start++] = max + 1
37
+ state.buffer[state.start++] = x >> 8 & 0xff
38
+ state.buffer[state.start++] = x & 0xff
39
+ } else if (num < 0x1000000) {
40
+ state.buffer[state.start++] = max + 2
41
+ state.buffer[state.start++] = x >> 16
42
+ state.buffer[state.start++] = x >> 8 & 0xff
43
+ state.buffer[state.start++] = x & 0xff
44
+ } else if (num < 0x100000000) {
45
+ state.buffer[state.start++] = max + 3
46
+ state.buffer[state.start++] = x >> 24
47
+ state.buffer[state.start++] = x >> 16 & 0xff
48
+ state.buffer[state.start++] = x >> 8 & 0xff
49
+ state.buffer[state.start++] = x & 0xff
50
+ } else {
51
+ // need to use Math here as bitwise ops are 32 bit
52
+ const exp = Math.floor(Math.log(x) / Math.log(2)) - 32
53
+ state.buffer[state.start++] = 0xff
54
+
55
+ encode(state, exp)
56
+ const rem = x / Math.pow(2, exp - 11)
57
+
58
+ for (let i = 5; i >= 0; i--) {
59
+ state.buffer[state.start++] = rem / Math.pow(2, 8 * i) & 0xff
60
+ }
61
+ }
62
+ }
63
+
64
+ function decode (state) {
65
+ const max = 251
66
+
67
+ if (state.end - state.start < 1) throw new Error('Out of bounds')
68
+
69
+ const flag = state.buffer[state.start++]
70
+
71
+ if (flag < max) return flag
72
+
73
+ if (state.end - state.start < flag - max + 1) {
74
+ throw new Error('Out of bounds.')
75
+ }
76
+
77
+ if (flag < 252) {
78
+ return state.buffer[state.start++] +
79
+ max
80
+ }
81
+
82
+ if (flag < 253) {
83
+ return (state.buffer[state.start++] << 8) +
84
+ state.buffer[state.start++] +
85
+ max
86
+ }
87
+
88
+ if (flag < 254) {
89
+ return (state.buffer[state.start++] << 16) +
90
+ (state.buffer[state.start++] << 8) +
91
+ state.buffer[state.start++] +
92
+ max
93
+ }
94
+
95
+ // << 24 result may be interpreted as negative
96
+ if (flag < 255) {
97
+ return (state.buffer[state.start++] * 0x1000000) +
98
+ (state.buffer[state.start++] << 16) +
99
+ (state.buffer[state.start++] << 8) +
100
+ state.buffer[state.start++] +
101
+ max
102
+ }
103
+
104
+ const exp = decode(state)
105
+
106
+ if (state.end - state.start < 6) throw new Error('Out of bounds')
107
+
108
+ let rem = 0
109
+ for (let i = 5; i >= 0; i--) {
110
+ rem += state.buffer[state.start++] * Math.pow(2, 8 * i)
111
+ }
112
+
113
+ return (rem * Math.pow(2, exp - 11)) + max
114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compact-encoding",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "A series of compact encoding schemes for building small and fast parsers and serializers",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/test.js CHANGED
@@ -407,3 +407,161 @@ tape('array', function (t) {
407
407
 
408
408
  t.exception(() => arr.decode(state))
409
409
  })
410
+
411
+ tape('lexint: big numbers', function (t) {
412
+ t.plan(1)
413
+
414
+ let prev = enc.encode(enc.lexint, 0)
415
+
416
+ let n
417
+ let skip = 1
418
+
419
+ for (n = 1; n < Number.MAX_VALUE; n += skip) {
420
+ const cur = enc.encode(enc.lexint, n)
421
+ if (Buffer.compare(cur, prev) < 1) break
422
+ prev = cur
423
+ skip = 1 + Math.pow(245, Math.ceil(Math.log(n) / Math.log(256)))
424
+ }
425
+ t.is(n, Infinity)
426
+ })
427
+
428
+ tape('lexint: range precision', function (t) {
429
+ t.plan(2)
430
+ const a = 1e55
431
+ const b = 1.0000000000001e55
432
+ const ha = enc.encode(enc.lexint, a).toString('hex')
433
+ const hb = enc.encode(enc.lexint, b).toString('hex')
434
+ t.not(a, b)
435
+ t.not(ha, hb)
436
+ })
437
+
438
+ tape('lexint: range precision', function (t) {
439
+ let prev = enc.encode(enc.lexint, 0)
440
+ const skip = 0.000000001e55
441
+ for (let i = 0, n = 1e55; i < 1000; n = 1e55 + skip * ++i) {
442
+ const cur = enc.encode(enc.lexint, n)
443
+ if (Buffer.compare(cur, prev) < 1) t.fail('cur <= prev')
444
+ prev = cur
445
+ }
446
+ t.ok(true)
447
+ t.end()
448
+ })
449
+
450
+ tape('lexint: small numbers', function (t) {
451
+ let prev = enc.encode(enc.lexint, 0)
452
+ for (let n = 1; n < 256 * 256 * 16; n++) {
453
+ const cur = enc.encode(enc.lexint, n)
454
+ if (Buffer.compare(cur, prev) < 1) t.fail('cur <= prev')
455
+ prev = cur
456
+ }
457
+ t.ok(true)
458
+ t.end()
459
+ })
460
+
461
+ tape('lexint: throws', function (t) {
462
+ t.exception(() => {
463
+ enc.decode(enc.lexint, Buffer.alloc(1, 251))
464
+ })
465
+
466
+ let num = 252
467
+
468
+ const state = {
469
+ start: 0,
470
+ end: 0,
471
+ buffer: null
472
+ }
473
+
474
+ enc.lexint.preencode(state, num)
475
+ state.buffer = Buffer.alloc(state.end - state.start)
476
+ enc.lexint.encode(state, num)
477
+
478
+ t.exception(() => {
479
+ enc.decode(enc.lexint, state.buffer.subarray(0, state.buffer.byteLength - 2))
480
+ })
481
+
482
+ num <<= 8
483
+
484
+ state.start = 0
485
+ state.end = 0
486
+ state.buffer = null
487
+
488
+ enc.lexint.preencode(state, num)
489
+ state.buffer = Buffer.alloc(state.end - state.start)
490
+ enc.lexint.encode(state, num)
491
+
492
+ t.exception(() => {
493
+ enc.decode(enc.lexint, state.buffer.subarray(0, state.buffer.byteLength - 2))
494
+ })
495
+
496
+ num <<= 8
497
+
498
+ state.start = 0
499
+ state.end = 0
500
+ state.buffer = null
501
+
502
+ enc.lexint.preencode(state, num)
503
+ state.buffer = Buffer.alloc(state.end - state.start)
504
+ enc.lexint.encode(state, num)
505
+
506
+ t.exception(() => {
507
+ enc.decode(enc.lexint, state.buffer.subarray(0, state.buffer.byteLength - 2))
508
+ })
509
+
510
+ num *= 256
511
+
512
+ state.start = 0
513
+ state.end = 0
514
+ state.buffer = null
515
+
516
+ enc.lexint.preencode(state, num)
517
+ state.buffer = Buffer.alloc(state.end - state.start)
518
+ enc.lexint.encode(state, num)
519
+
520
+ t.exception(() => {
521
+ enc.decode(enc.lexint, state.buffer.subarray(0, state.buffer.byteLength - 2))
522
+ })
523
+
524
+ num *= 256 * 256
525
+
526
+ state.start = 0
527
+ state.end = 0
528
+ state.buffer = null
529
+
530
+ enc.lexint.preencode(state, num)
531
+ state.buffer = Buffer.alloc(state.end - state.start)
532
+ enc.lexint.encode(state, num)
533
+
534
+ t.exception(() => {
535
+ enc.decode(enc.lexint, state.buffer.subarray(0, state.buffer.byteLength - 2))
536
+ })
537
+
538
+ t.end()
539
+ })
540
+
541
+ tape('lexint: unpack', function (t) {
542
+ let n
543
+ let skip = 1
544
+
545
+ for (n = 1; n < Number.MAX_VALUE; n += skip) {
546
+ const cur = enc.encode(enc.lexint, n)
547
+ compare(n, enc.decode(enc.lexint, cur))
548
+ skip = 1 + Math.pow(245, Math.ceil(Math.log(n) / Math.log(256)))
549
+ }
550
+ t.is(n, Infinity)
551
+ t.end()
552
+
553
+ function compare (a, b) {
554
+ const desc = a + ' !=~ ' + b
555
+ if (/e\+\d+$/.test(a) || /e\+\d+$/.test(b)) {
556
+ if (String(a).slice(0, 8) !== String(b).slice(0, 8) ||
557
+ /e\+(\d+)$/.exec(a)[1] !== /e\+(\d+)$/.exec(b)[1]) {
558
+ t.fail(desc)
559
+ }
560
+ } else {
561
+ if (String(a).slice(0, 8) !== String(b).slice(0, 8) ||
562
+ String(a).length !== String(b).length) {
563
+ t.fail(desc)
564
+ }
565
+ }
566
+ }
567
+ })