/
leb128.ts
228 lines (206 loc) · 5.93 KB
/
leb128.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/* eslint-disable no-constant-condition */
// tslint:disable:no-bitwise
// Note: this file uses buffer-pipe, which on Node only, uses the Node Buffer
// implementation, which isn't compatible with the NPM buffer package
// which we use everywhere else. This means that we have to transform
// one into the other, hence why every function that returns a Buffer
// actually return `new Buffer(pipe.buffer)`.
// TODO: The best solution would be to have our own buffer type around
// Uint8Array which is standard.
import { PipeArrayBuffer as Pipe } from './buffer';
function eob(): never {
throw new Error('unexpected end of buffer');
}
/**
*
* @param pipe Pipe from buffer-pipe
* @param num number
* @returns Buffer
*/
export function safeRead(pipe: Pipe, num: number): ArrayBuffer {
if (pipe.byteLength < num) {
eob();
}
return pipe.read(num);
}
/**
* @param pipe
*/
export function safeReadUint8(pipe: Pipe): number {
const byte = pipe.readUint8();
if (byte === undefined) {
eob();
}
return byte;
}
/**
* Encode a positive number (or bigint) into a Buffer. The number will be floored to the
* nearest integer.
* @param value The number to encode.
*/
export function lebEncode(value: bigint | number): ArrayBuffer {
if (typeof value === 'number') {
value = BigInt(value);
}
if (value < BigInt(0)) {
throw new Error('Cannot leb encode negative values.');
}
const byteLength = (value === BigInt(0) ? 0 : Math.ceil(Math.log2(Number(value)))) + 1;
const pipe = new Pipe(new ArrayBuffer(byteLength), 0);
while (true) {
const i = Number(value & BigInt(0x7f));
value /= BigInt(0x80);
if (value === BigInt(0)) {
pipe.write(new Uint8Array([i]));
break;
} else {
pipe.write(new Uint8Array([i | 0x80]));
}
}
return pipe.buffer;
}
/**
* Decode a leb encoded buffer into a bigint. The number will always be positive (does not
* support signed leb encoding).
* @param pipe A Buffer containing the leb encoded bits.
*/
export function lebDecode(pipe: Pipe): bigint {
let weight = BigInt(1);
let value = BigInt(0);
let byte;
do {
byte = safeReadUint8(pipe);
value += BigInt(byte & 0x7f).valueOf() * weight;
weight *= BigInt(128);
} while (byte >= 0x80);
return value;
}
/**
* Encode a number (or bigint) into a Buffer, with support for negative numbers. The number
* will be floored to the nearest integer.
* @param value The number to encode.
*/
export function slebEncode(value: bigint | number): ArrayBuffer {
if (typeof value === 'number') {
value = BigInt(value);
}
const isNeg = value < BigInt(0);
if (isNeg) {
value = -value - BigInt(1);
}
const byteLength = (value === BigInt(0) ? 0 : Math.ceil(Math.log2(Number(value)))) + 1;
const pipe = new Pipe(new ArrayBuffer(byteLength), 0);
while (true) {
const i = getLowerBytes(value);
value /= BigInt(0x80);
// prettier-ignore
if ( ( isNeg && value === BigInt(0) && (i & 0x40) !== 0)
|| (!isNeg && value === BigInt(0) && (i & 0x40) === 0)) {
pipe.write(new Uint8Array([i]));
break;
} else {
pipe.write(new Uint8Array([i | 0x80]));
}
}
function getLowerBytes(num: bigint): number {
const bytes = num % BigInt(0x80);
if (isNeg) {
// We swap the bits here again, and remove 1 to do two's complement.
return Number(BigInt(0x80) - bytes - BigInt(1));
} else {
return Number(bytes);
}
}
return pipe.buffer;
}
/**
* Decode a leb encoded buffer into a bigint. The number is decoded with support for negative
* signed-leb encoding.
* @param pipe A Buffer containing the signed leb encoded bits.
*/
export function slebDecode(pipe: Pipe): bigint {
// Get the size of the buffer, then cut a buffer of that size.
const pipeView = new Uint8Array(pipe.buffer);
let len = 0;
for (; len < pipeView.byteLength; len++) {
if (pipeView[len] < 0x80) {
// If it's a positive number, we reuse lebDecode.
if ((pipeView[len] & 0x40) === 0) {
return lebDecode(pipe);
}
break;
}
}
const bytes = new Uint8Array(safeRead(pipe, len + 1));
let value = BigInt(0);
for (let i = bytes.byteLength - 1; i >= 0; i--) {
value = value * BigInt(0x80) + BigInt(0x80 - (bytes[i] & 0x7f) - 1);
}
return -value - BigInt(1);
}
/**
*
* @param value bigint or number
* @param byteLength number
* @returns Buffer
*/
export function writeUIntLE(value: bigint | number, byteLength: number): ArrayBuffer {
if (BigInt(value) < BigInt(0)) {
throw new Error('Cannot write negative values.');
}
return writeIntLE(value, byteLength);
}
/**
*
* @param value
* @param byteLength
*/
export function writeIntLE(value: bigint | number, byteLength: number): ArrayBuffer {
value = BigInt(value);
const pipe = new Pipe(new ArrayBuffer(Math.min(1, byteLength)), 0);
let i = 0;
let mul = BigInt(256);
let sub = BigInt(0);
let byte = Number(value % mul);
pipe.write(new Uint8Array([byte]));
while (++i < byteLength) {
if (value < 0 && sub === BigInt(0) && byte !== 0) {
sub = BigInt(1);
}
byte = Number((value / mul - sub) % BigInt(256));
pipe.write(new Uint8Array([byte]));
mul *= BigInt(256);
}
return pipe.buffer;
}
/**
*
* @param pipe Pipe from buffer-pipe
* @param byteLength number
* @returns bigint
*/
export function readUIntLE(pipe: Pipe, byteLength: number): bigint {
let val = BigInt(safeReadUint8(pipe));
let mul = BigInt(1);
let i = 0;
while (++i < byteLength) {
mul *= BigInt(256);
const byte = BigInt(safeReadUint8(pipe));
val = val + mul * byte;
}
return val;
}
/**
*
* @param pipe Pipe from buffer-pipe
* @param byteLength number
* @returns bigint
*/
export function readIntLE(pipe: Pipe, byteLength: number): bigint {
let val = readUIntLE(pipe, byteLength);
const mul = BigInt(2) ** (BigInt(8) * BigInt(byteLength - 1) + BigInt(7));
if (val >= mul) {
val -= mul * BigInt(2);
}
return val;
}