UNPKG

17.6 kBJavaScriptView Raw
1/**
2 * Efficient schema-less binary decoding with support for variable length encoding.
3 *
4 * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
5 *
6 * Encodes numbers in little-endian order (least to most significant byte order)
7 * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
8 * which is also used in Protocol Buffers.
9 *
10 * ```js
11 * // encoding step
12 * const encoder = encoding.createEncoder()
13 * encoding.writeVarUint(encoder, 256)
14 * encoding.writeVarString(encoder, 'Hello world!')
15 * const buf = encoding.toUint8Array(encoder)
16 * ```
17 *
18 * ```js
19 * // decoding step
20 * const decoder = decoding.createDecoder(buf)
21 * decoding.readVarUint(decoder) // => 256
22 * decoding.readVarString(decoder) // => 'Hello world!'
23 * decoding.hasContent(decoder) // => false - all data is read
24 * ```
25 *
26 * @module decoding
27 */
28
29import * as binary from './binary.js'
30import * as math from './math.js'
31import * as number from './number.js'
32import * as string from './string.js'
33import * as error from './error.js'
34import * as encoding from './encoding.js'
35
36const errorUnexpectedEndOfArray = error.create('Unexpected end of array')
37const errorIntegerOutOfRange = error.create('Integer out of Range')
38
39/**
40 * A Decoder handles the decoding of an Uint8Array.
41 */
42export class Decoder {
43 /**
44 * @param {Uint8Array} uint8Array Binary data to decode
45 */
46 constructor (uint8Array) {
47 /**
48 * Decoding target.
49 *
50 * @type {Uint8Array}
51 */
52 this.arr = uint8Array
53 /**
54 * Current decoding position.
55 *
56 * @type {number}
57 */
58 this.pos = 0
59 }
60}
61
62/**
63 * @function
64 * @param {Uint8Array} uint8Array
65 * @return {Decoder}
66 */
67export const createDecoder = uint8Array => new Decoder(uint8Array)
68
69/**
70 * @function
71 * @param {Decoder} decoder
72 * @return {boolean}
73 */
74export const hasContent = decoder => decoder.pos !== decoder.arr.length
75
76/**
77 * Clone a decoder instance.
78 * Optionally set a new position parameter.
79 *
80 * @function
81 * @param {Decoder} decoder The decoder instance
82 * @param {number} [newPos] Defaults to current position
83 * @return {Decoder} A clone of `decoder`
84 */
85export const clone = (decoder, newPos = decoder.pos) => {
86 const _decoder = createDecoder(decoder.arr)
87 _decoder.pos = newPos
88 return _decoder
89}
90
91/**
92 * Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
93 *
94 * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
95 * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
96 *
97 * @function
98 * @param {Decoder} decoder The decoder instance
99 * @param {number} len The length of bytes to read
100 * @return {Uint8Array}
101 */
102export const readUint8Array = (decoder, len) => {
103 const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len)
104 decoder.pos += len
105 return view
106}
107
108/**
109 * Read variable length Uint8Array.
110 *
111 * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
112 * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
113 *
114 * @function
115 * @param {Decoder} decoder
116 * @return {Uint8Array}
117 */
118export const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder))
119
120/**
121 * Read the rest of the content as an ArrayBuffer
122 * @function
123 * @param {Decoder} decoder
124 * @return {Uint8Array}
125 */
126export const readTailAsUint8Array = decoder => readUint8Array(decoder, decoder.arr.length - decoder.pos)
127
128/**
129 * Skip one byte, jump to the next position.
130 * @function
131 * @param {Decoder} decoder The decoder instance
132 * @return {number} The next position
133 */
134export const skip8 = decoder => decoder.pos++
135
136/**
137 * Read one byte as unsigned integer.
138 * @function
139 * @param {Decoder} decoder The decoder instance
140 * @return {number} Unsigned 8-bit integer
141 */
142export const readUint8 = decoder => decoder.arr[decoder.pos++]
143
144/**
145 * Read 2 bytes as unsigned integer.
146 *
147 * @function
148 * @param {Decoder} decoder
149 * @return {number} An unsigned integer.
150 */
151export const readUint16 = decoder => {
152 const uint =
153 decoder.arr[decoder.pos] +
154 (decoder.arr[decoder.pos + 1] << 8)
155 decoder.pos += 2
156 return uint
157}
158
159/**
160 * Read 4 bytes as unsigned integer.
161 *
162 * @function
163 * @param {Decoder} decoder
164 * @return {number} An unsigned integer.
165 */
166export const readUint32 = decoder => {
167 const uint =
168 (decoder.arr[decoder.pos] +
169 (decoder.arr[decoder.pos + 1] << 8) +
170 (decoder.arr[decoder.pos + 2] << 16) +
171 (decoder.arr[decoder.pos + 3] << 24)) >>> 0
172 decoder.pos += 4
173 return uint
174}
175
176/**
177 * Read 4 bytes as unsigned integer in big endian order.
178 * (most significant byte first)
179 *
180 * @function
181 * @param {Decoder} decoder
182 * @return {number} An unsigned integer.
183 */
184export const readUint32BigEndian = decoder => {
185 const uint =
186 (decoder.arr[decoder.pos + 3] +
187 (decoder.arr[decoder.pos + 2] << 8) +
188 (decoder.arr[decoder.pos + 1] << 16) +
189 (decoder.arr[decoder.pos] << 24)) >>> 0
190 decoder.pos += 4
191 return uint
192}
193
194/**
195 * Look ahead without incrementing the position
196 * to the next byte and read it as unsigned integer.
197 *
198 * @function
199 * @param {Decoder} decoder
200 * @return {number} An unsigned integer.
201 */
202export const peekUint8 = decoder => decoder.arr[decoder.pos]
203
204/**
205 * Look ahead without incrementing the position
206 * to the next byte and read it as unsigned integer.
207 *
208 * @function
209 * @param {Decoder} decoder
210 * @return {number} An unsigned integer.
211 */
212export const peekUint16 = decoder =>
213 decoder.arr[decoder.pos] +
214 (decoder.arr[decoder.pos + 1] << 8)
215
216/**
217 * Look ahead without incrementing the position
218 * to the next byte and read it as unsigned integer.
219 *
220 * @function
221 * @param {Decoder} decoder
222 * @return {number} An unsigned integer.
223 */
224export const peekUint32 = decoder => (
225 decoder.arr[decoder.pos] +
226 (decoder.arr[decoder.pos + 1] << 8) +
227 (decoder.arr[decoder.pos + 2] << 16) +
228 (decoder.arr[decoder.pos + 3] << 24)
229) >>> 0
230
231/**
232 * Read unsigned integer (32bit) with variable length.
233 * 1/8th of the storage is used as encoding overhead.
234 * * numbers < 2^7 is stored in one bytlength
235 * * numbers < 2^14 is stored in two bylength
236 *
237 * @function
238 * @param {Decoder} decoder
239 * @return {number} An unsigned integer.length
240 */
241export const readVarUint = decoder => {
242 let num = 0
243 let mult = 1
244 const len = decoder.arr.length
245 while (decoder.pos < len) {
246 const r = decoder.arr[decoder.pos++]
247 // num = num | ((r & binary.BITS7) << len)
248 num = num + (r & binary.BITS7) * mult // shift $r << (7*#iterations) and add it to num
249 mult *= 128 // next iteration, shift 7 "more" to the left
250 if (r < binary.BIT8) {
251 return num
252 }
253 /* c8 ignore start */
254 if (num > number.MAX_SAFE_INTEGER) {
255 throw errorIntegerOutOfRange
256 }
257 /* c8 ignore stop */
258 }
259 throw errorUnexpectedEndOfArray
260}
261
262/**
263 * Read signed integer (32bit) with variable length.
264 * 1/8th of the storage is used as encoding overhead.
265 * * numbers < 2^7 is stored in one bytlength
266 * * numbers < 2^14 is stored in two bylength
267 * @todo This should probably create the inverse ~num if number is negative - but this would be a breaking change.
268 *
269 * @function
270 * @param {Decoder} decoder
271 * @return {number} An unsigned integer.length
272 */
273export const readVarInt = decoder => {
274 let r = decoder.arr[decoder.pos++]
275 let num = r & binary.BITS6
276 let mult = 64
277 const sign = (r & binary.BIT7) > 0 ? -1 : 1
278 if ((r & binary.BIT8) === 0) {
279 // don't continue reading
280 return sign * num
281 }
282 const len = decoder.arr.length
283 while (decoder.pos < len) {
284 r = decoder.arr[decoder.pos++]
285 // num = num | ((r & binary.BITS7) << len)
286 num = num + (r & binary.BITS7) * mult
287 mult *= 128
288 if (r < binary.BIT8) {
289 return sign * num
290 }
291 /* c8 ignore start */
292 if (num > number.MAX_SAFE_INTEGER) {
293 throw errorIntegerOutOfRange
294 }
295 /* c8 ignore stop */
296 }
297 throw errorUnexpectedEndOfArray
298}
299
300/**
301 * Look ahead and read varUint without incrementing position
302 *
303 * @function
304 * @param {Decoder} decoder
305 * @return {number}
306 */
307export const peekVarUint = decoder => {
308 const pos = decoder.pos
309 const s = readVarUint(decoder)
310 decoder.pos = pos
311 return s
312}
313
314/**
315 * Look ahead and read varUint without incrementing position
316 *
317 * @function
318 * @param {Decoder} decoder
319 * @return {number}
320 */
321export const peekVarInt = decoder => {
322 const pos = decoder.pos
323 const s = readVarInt(decoder)
324 decoder.pos = pos
325 return s
326}
327
328/**
329 * We don't test this function anymore as we use native decoding/encoding by default now.
330 * Better not modify this anymore..
331 *
332 * Transforming utf8 to a string is pretty expensive. The code performs 10x better
333 * when String.fromCodePoint is fed with all characters as arguments.
334 * But most environments have a maximum number of arguments per functions.
335 * For effiency reasons we apply a maximum of 10000 characters at once.
336 *
337 * @function
338 * @param {Decoder} decoder
339 * @return {String} The read String.
340 */
341/* c8 ignore start */
342export const _readVarStringPolyfill = decoder => {
343 let remainingLen = readVarUint(decoder)
344 if (remainingLen === 0) {
345 return ''
346 } else {
347 let encodedString = String.fromCodePoint(readUint8(decoder)) // remember to decrease remainingLen
348 if (--remainingLen < 100) { // do not create a Uint8Array for small strings
349 while (remainingLen--) {
350 encodedString += String.fromCodePoint(readUint8(decoder))
351 }
352 } else {
353 while (remainingLen > 0) {
354 const nextLen = remainingLen < 10000 ? remainingLen : 10000
355 // this is dangerous, we create a fresh array view from the existing buffer
356 const bytes = decoder.arr.subarray(decoder.pos, decoder.pos + nextLen)
357 decoder.pos += nextLen
358 // Starting with ES5.1 we can supply a generic array-like object as arguments
359 encodedString += String.fromCodePoint.apply(null, /** @type {any} */ (bytes))
360 remainingLen -= nextLen
361 }
362 }
363 return decodeURIComponent(escape(encodedString))
364 }
365}
366/* c8 ignore stop */
367
368/**
369 * @function
370 * @param {Decoder} decoder
371 * @return {String} The read String
372 */
373export const _readVarStringNative = decoder =>
374 /** @type any */ (string.utf8TextDecoder).decode(readVarUint8Array(decoder))
375
376/**
377 * Read string of variable length
378 * * varUint is used to store the length of the string
379 *
380 * @function
381 * @param {Decoder} decoder
382 * @return {String} The read String
383 *
384 */
385/* c8 ignore next */
386export const readVarString = string.utf8TextDecoder ? _readVarStringNative : _readVarStringPolyfill
387
388/**
389 * @param {Decoder} decoder
390 * @return {Uint8Array}
391 */
392export const readTerminatedUint8Array = decoder => {
393 const encoder = encoding.createEncoder()
394 let b
395 while (true) {
396 b = readUint8(decoder)
397 if (b === 0) {
398 return encoding.toUint8Array(encoder)
399 }
400 if (b === 1) {
401 b = readUint8(decoder)
402 }
403 encoding.write(encoder, b)
404 }
405}
406
407/**
408 * @param {Decoder} decoder
409 * @return {string}
410 */
411export const readTerminatedString = decoder => string.decodeUtf8(readTerminatedUint8Array(decoder))
412
413/**
414 * Look ahead and read varString without incrementing position
415 *
416 * @function
417 * @param {Decoder} decoder
418 * @return {string}
419 */
420export const peekVarString = decoder => {
421 const pos = decoder.pos
422 const s = readVarString(decoder)
423 decoder.pos = pos
424 return s
425}
426
427/**
428 * @param {Decoder} decoder
429 * @param {number} len
430 * @return {DataView}
431 */
432export const readFromDataView = (decoder, len) => {
433 const dv = new DataView(decoder.arr.buffer, decoder.arr.byteOffset + decoder.pos, len)
434 decoder.pos += len
435 return dv
436}
437
438/**
439 * @param {Decoder} decoder
440 */
441export const readFloat32 = decoder => readFromDataView(decoder, 4).getFloat32(0, false)
442
443/**
444 * @param {Decoder} decoder
445 */
446export const readFloat64 = decoder => readFromDataView(decoder, 8).getFloat64(0, false)
447
448/**
449 * @param {Decoder} decoder
450 */
451export const readBigInt64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigInt64(0, false)
452
453/**
454 * @param {Decoder} decoder
455 */
456export const readBigUint64 = decoder => /** @type {any} */ (readFromDataView(decoder, 8)).getBigUint64(0, false)
457
458/**
459 * @type {Array<function(Decoder):any>}
460 */
461const readAnyLookupTable = [
462 decoder => undefined, // CASE 127: undefined
463 decoder => null, // CASE 126: null
464 readVarInt, // CASE 125: integer
465 readFloat32, // CASE 124: float32
466 readFloat64, // CASE 123: float64
467 readBigInt64, // CASE 122: bigint
468 decoder => false, // CASE 121: boolean (false)
469 decoder => true, // CASE 120: boolean (true)
470 readVarString, // CASE 119: string
471 decoder => { // CASE 118: object<string,any>
472 const len = readVarUint(decoder)
473 /**
474 * @type {Object<string,any>}
475 */
476 const obj = {}
477 for (let i = 0; i < len; i++) {
478 const key = readVarString(decoder)
479 obj[key] = readAny(decoder)
480 }
481 return obj
482 },
483 decoder => { // CASE 117: array<any>
484 const len = readVarUint(decoder)
485 const arr = []
486 for (let i = 0; i < len; i++) {
487 arr.push(readAny(decoder))
488 }
489 return arr
490 },
491 readVarUint8Array // CASE 116: Uint8Array
492]
493
494/**
495 * @param {Decoder} decoder
496 */
497export const readAny = decoder => readAnyLookupTable[127 - readUint8(decoder)](decoder)
498
499/**
500 * T must not be null.
501 *
502 * @template T
503 */
504export class RleDecoder extends Decoder {
505 /**
506 * @param {Uint8Array} uint8Array
507 * @param {function(Decoder):T} reader
508 */
509 constructor (uint8Array, reader) {
510 super(uint8Array)
511 /**
512 * The reader
513 */
514 this.reader = reader
515 /**
516 * Current state
517 * @type {T|null}
518 */
519 this.s = null
520 this.count = 0
521 }
522
523 read () {
524 if (this.count === 0) {
525 this.s = this.reader(this)
526 if (hasContent(this)) {
527 this.count = readVarUint(this) + 1 // see encoder implementation for the reason why this is incremented
528 } else {
529 this.count = -1 // read the current value forever
530 }
531 }
532 this.count--
533 return /** @type {T} */ (this.s)
534 }
535}
536
537export class IntDiffDecoder extends Decoder {
538 /**
539 * @param {Uint8Array} uint8Array
540 * @param {number} start
541 */
542 constructor (uint8Array, start) {
543 super(uint8Array)
544 /**
545 * Current state
546 * @type {number}
547 */
548 this.s = start
549 }
550
551 /**
552 * @return {number}
553 */
554 read () {
555 this.s += readVarInt(this)
556 return this.s
557 }
558}
559
560export class RleIntDiffDecoder extends Decoder {
561 /**
562 * @param {Uint8Array} uint8Array
563 * @param {number} start
564 */
565 constructor (uint8Array, start) {
566 super(uint8Array)
567 /**
568 * Current state
569 * @type {number}
570 */
571 this.s = start
572 this.count = 0
573 }
574
575 /**
576 * @return {number}
577 */
578 read () {
579 if (this.count === 0) {
580 this.s += readVarInt(this)
581 if (hasContent(this)) {
582 this.count = readVarUint(this) + 1 // see encoder implementation for the reason why this is incremented
583 } else {
584 this.count = -1 // read the current value forever
585 }
586 }
587 this.count--
588 return /** @type {number} */ (this.s)
589 }
590}
591
592export class UintOptRleDecoder extends Decoder {
593 /**
594 * @param {Uint8Array} uint8Array
595 */
596 constructor (uint8Array) {
597 super(uint8Array)
598 /**
599 * @type {number}
600 */
601 this.s = 0
602 this.count = 0
603 }
604
605 read () {
606 if (this.count === 0) {
607 this.s = readVarInt(this)
608 // if the sign is negative, we read the count too, otherwise count is 1
609 const isNegative = math.isNegativeZero(this.s)
610 this.count = 1
611 if (isNegative) {
612 this.s = -this.s
613 this.count = readVarUint(this) + 2
614 }
615 }
616 this.count--
617 return /** @type {number} */ (this.s)
618 }
619}
620
621export class IncUintOptRleDecoder extends Decoder {
622 /**
623 * @param {Uint8Array} uint8Array
624 */
625 constructor (uint8Array) {
626 super(uint8Array)
627 /**
628 * @type {number}
629 */
630 this.s = 0
631 this.count = 0
632 }
633
634 read () {
635 if (this.count === 0) {
636 this.s = readVarInt(this)
637 // if the sign is negative, we read the count too, otherwise count is 1
638 const isNegative = math.isNegativeZero(this.s)
639 this.count = 1
640 if (isNegative) {
641 this.s = -this.s
642 this.count = readVarUint(this) + 2
643 }
644 }
645 this.count--
646 return /** @type {number} */ (this.s++)
647 }
648}
649
650export class IntDiffOptRleDecoder extends Decoder {
651 /**
652 * @param {Uint8Array} uint8Array
653 */
654 constructor (uint8Array) {
655 super(uint8Array)
656 /**
657 * @type {number}
658 */
659 this.s = 0
660 this.count = 0
661 this.diff = 0
662 }
663
664 /**
665 * @return {number}
666 */
667 read () {
668 if (this.count === 0) {
669 const diff = readVarInt(this)
670 // if the first bit is set, we read more data
671 const hasCount = diff & 1
672 this.diff = math.floor(diff / 2) // shift >> 1
673 this.count = 1
674 if (hasCount) {
675 this.count = readVarUint(this) + 2
676 }
677 }
678 this.s += this.diff
679 this.count--
680 return this.s
681 }
682}
683
684export class StringDecoder {
685 /**
686 * @param {Uint8Array} uint8Array
687 */
688 constructor (uint8Array) {
689 this.decoder = new UintOptRleDecoder(uint8Array)
690 this.str = readVarString(this.decoder)
691 /**
692 * @type {number}
693 */
694 this.spos = 0
695 }
696
697 /**
698 * @return {string}
699 */
700 read () {
701 const end = this.spos + this.decoder.read()
702 const res = this.str.slice(this.spos, end)
703 this.spos = end
704 return res
705 }
706}