libshv-js 3.0.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.
@@ -0,0 +1,590 @@
1
+ import {utf8ToString} from './cpon.ts';
2
+ import {type RpcValue, type RpcValueType, type DateTime, Decimal, Double, IMap, Int, MetaMap, RpcValueWithMetaData, ShvMap, UInt, withOffset} from './rpcvalue.ts';
3
+ import {UnpackContext, PackContext} from './cpcontext.ts';
4
+
5
+ enum PackingSchema {
6
+ IntThreshold = 64,
7
+ Null = 128,
8
+ UInt = 129,
9
+ Int = 130,
10
+ Double = 131,
11
+ Bool = 132,
12
+ Blob = 133,
13
+ String = 134,
14
+ List = 136,
15
+ Map = 137,
16
+ IMap = 138,
17
+ MetaMap = 139,
18
+ Decimal = 140,
19
+ DateTime = 141,
20
+ CString = 142,
21
+ False = 253,
22
+ True = 254,
23
+ Term = 255,
24
+ }
25
+
26
+ const SHV_EPOCH_MSEC = 1_517_529_600_000;
27
+ const INVALID_MIN_OFFSET_FROM_UTC = (-64 * 15);
28
+
29
+ const div_int = (n: bigint, d: bigint) => {
30
+ const r = n % d;
31
+ return [(n - r) / d, r] as const;
32
+ };
33
+
34
+ const uint8_array_to_bigint = (bytes: Uint8Array, type: 'Int' | 'UInt') => {
35
+ let is_neg = false;
36
+
37
+ if (type === 'Int') {
38
+ if (bytes.length < 5) {
39
+ const sign_mask = 0x80 >> bytes.length;
40
+ is_neg = Boolean(bytes[0] & sign_mask);
41
+ bytes[0] &= ~sign_mask;
42
+ } else {
43
+ is_neg = Boolean(bytes[1] & 128);
44
+ bytes[1] &= ~128;
45
+ }
46
+ }
47
+
48
+ let ret = 0n;
49
+ for (const byte of bytes) {
50
+ ret = (ret * BigInt(256)) + BigInt(byte);
51
+ }
52
+
53
+ if (is_neg) {
54
+ ret = -ret;
55
+ }
56
+
57
+ return ret;
58
+ };
59
+
60
+ const number_to_uint8_array = (num: bigint) => {
61
+ let bytes = new Uint8Array(8);
62
+ let len = 0;
63
+ while (true) {
64
+ const res = div_int(num, 256n);
65
+ num = res[0];
66
+ bytes[len++] = Number(res[1]);
67
+ if (num === 0n) {
68
+ break;
69
+ }
70
+ }
71
+
72
+ bytes = bytes.subarray(0, len);
73
+ bytes.reverse();
74
+ return bytes;
75
+ };
76
+
77
+ const significant_bits_count = (num: bigint) => {
78
+ let ret = 0;
79
+ while (num > 0) {
80
+ ret++;
81
+ num >>= 1n;
82
+ }
83
+ return ret;
84
+ };
85
+
86
+ class ChainPackReader {
87
+ ctx: UnpackContext;
88
+
89
+ constructor(data: ArrayBuffer | Uint8Array | UnpackContext) {
90
+ this.ctx = data instanceof UnpackContext ? data : new UnpackContext(data);
91
+ }
92
+
93
+ read(): RpcValue {
94
+ let meta: MetaMap | undefined;
95
+ let codePointAt = this.ctx.getByte() as PackingSchema;
96
+
97
+ if (codePointAt === PackingSchema.MetaMap) {
98
+ meta = this.readMetaMap();
99
+ codePointAt = this.ctx.getByte();
100
+ }
101
+
102
+ const impl_return = (x: RpcValueType) => {
103
+ const ret = meta !== undefined ? new RpcValueWithMetaData(x, meta) : x;
104
+ return ret;
105
+ };
106
+
107
+ if (codePointAt < PackingSchema.Null) {
108
+ if (codePointAt >= PackingSchema.IntThreshold) {
109
+ return impl_return(new Int(codePointAt - 64));
110
+ }
111
+ return impl_return(new UInt(codePointAt));
112
+ }
113
+ switch (codePointAt) {
114
+ case PackingSchema.Null: {
115
+ return impl_return(undefined);
116
+ }
117
+ case PackingSchema.True: {
118
+ return impl_return(true);
119
+ }
120
+ case PackingSchema.False: {
121
+ return impl_return(false);
122
+ }
123
+ case PackingSchema.Int: {
124
+ return impl_return(new Int(this.readIntData()));
125
+ }
126
+ case PackingSchema.UInt: {
127
+ return impl_return(new UInt(this.readUIntData()));
128
+ }
129
+ case PackingSchema.Double: {
130
+ const data = new Uint8Array(8);
131
+ for (let i = 0; i < 8; i++) {
132
+ data[i] = this.ctx.getByte();
133
+ }
134
+ return impl_return(new Double(new DataView(data.buffer).getFloat64(0, true))); // little endian
135
+ }
136
+ case PackingSchema.Decimal: {
137
+ const mant = this.readIntData();
138
+ const exp = this.readIntData();
139
+ return impl_return(new Decimal(mant, exp));
140
+ }
141
+ case PackingSchema.DateTime: {
142
+ let bi = this.readUIntDataHelper('UInt');
143
+ const has_tz_offset = bi & 1n;
144
+ const has_not_msec = bi & 2n;
145
+ bi >>= 2n;
146
+
147
+ let offset = 0;
148
+ if (has_tz_offset) {
149
+ offset = Number(bi & 0x7Fn);
150
+ if (offset & 0x40) {
151
+ // sign extension
152
+ offset -= 128;
153
+ }
154
+ bi >>= 7n;
155
+ }
156
+
157
+ offset *= 15;
158
+
159
+ if (offset === INVALID_MIN_OFFSET_FROM_UTC) {
160
+ return impl_return(undefined);
161
+ }
162
+
163
+ let msec = Number(bi);
164
+ if (has_not_msec) {
165
+ msec *= 1000;
166
+ }
167
+ msec += SHV_EPOCH_MSEC;
168
+ msec -= offset * 60_000;
169
+ return withOffset(new Date(msec), offset);
170
+ }
171
+ case PackingSchema.Map: {
172
+ return impl_return(this.readMap());
173
+ }
174
+ case PackingSchema.IMap: {
175
+ return impl_return(this.readIMap());
176
+ }
177
+ case PackingSchema.List: {
178
+ return impl_return(this.readList());
179
+ }
180
+ case PackingSchema.Blob: {
181
+ const str_len = this.readUIntData();
182
+ const arr = new Uint8Array(str_len);
183
+ for (let i = 0; i < str_len; i++) {
184
+ arr[i] = this.ctx.getByte();
185
+ }
186
+ return impl_return(arr.buffer);
187
+ }
188
+ case PackingSchema.String: {
189
+ const str_len = this.readUIntData();
190
+ const arr = new Uint8Array(str_len);
191
+ for (let i = 0; i < str_len; i++) {
192
+ arr[i] = this.ctx.getByte();
193
+ }
194
+ return impl_return(utf8ToString(arr.buffer));
195
+ }
196
+ case PackingSchema.CString: {
197
+ // variation of CponReader.readCString()
198
+ const pctx = new PackContext();
199
+ while (true) {
200
+ let b = this.ctx.getByte();
201
+ if (b === '\\'.codePointAt(0)) {
202
+ b = this.ctx.getByte();
203
+ switch (b) {
204
+ case '\\'.codePointAt(0):
205
+ pctx.putByte('\\'.codePointAt(0)!);
206
+ break;
207
+ case '0'.codePointAt(0):
208
+ pctx.putByte(0);
209
+ break;
210
+ default:
211
+ pctx.putByte(b);
212
+ break;
213
+ }
214
+ } else if (b === 0) {
215
+ // end of string
216
+ break;
217
+ } else {
218
+ pctx.putByte(b);
219
+ }
220
+ }
221
+ return impl_return(utf8ToString(pctx.buffer()));
222
+ }
223
+ default:
224
+ throw new TypeError('ChainPack - Invalid type info: ' + codePointAt);
225
+ }
226
+ }
227
+
228
+ readUIntDataHelper(type: 'Int' | 'UInt') {
229
+ let num = 0;
230
+ const head = this.ctx.getByte();
231
+ let bytes_to_read_cnt;
232
+ switch (0) {
233
+ case head & 128: {
234
+ bytes_to_read_cnt = 0;
235
+ num = head & 127;
236
+ break;
237
+ }
238
+ case head & 64: {
239
+ bytes_to_read_cnt = 1;
240
+ num = head & 63;
241
+ break;
242
+ }
243
+ case head & 32: {
244
+ bytes_to_read_cnt = 2;
245
+ num = head & 31;
246
+ break;
247
+ }
248
+ case head & 16: {
249
+ bytes_to_read_cnt = 3;
250
+ num = head & 15;
251
+ break;
252
+ }
253
+ default: {
254
+ bytes_to_read_cnt = (head & 0xF) + 4;
255
+ }
256
+ }
257
+ const bytes = new Uint8Array(bytes_to_read_cnt + 1);
258
+ bytes[0] = num;
259
+ for (let i = 0; i < bytes_to_read_cnt; i++) {
260
+ const r = this.ctx.getByte();
261
+ bytes[i + 1] = r;
262
+ }
263
+
264
+ return uint8_array_to_bigint(bytes, type);
265
+ }
266
+
267
+ readIntData() {
268
+ const val = this.readUIntDataHelper('Int');
269
+ if (val <= Number.MIN_SAFE_INTEGER) {
270
+ return Number.MIN_SAFE_INTEGER;
271
+ }
272
+ if (val >= Number.MAX_SAFE_INTEGER) {
273
+ return Number.MAX_SAFE_INTEGER;
274
+ }
275
+ return Number(val);
276
+ }
277
+
278
+ readUIntData() {
279
+ const val = this.readUIntDataHelper('UInt');
280
+ if (val >= Number.MAX_SAFE_INTEGER) {
281
+ return Number.MAX_SAFE_INTEGER;
282
+ }
283
+ return Number(val);
284
+ }
285
+
286
+ readList() {
287
+ const lst = [];
288
+ while (true) {
289
+ const b = this.ctx.peekByte();
290
+ if (b as PackingSchema === PackingSchema.Term) {
291
+ this.ctx.getByte();
292
+ break;
293
+ }
294
+ lst.push(this.read());
295
+ }
296
+ return lst;
297
+ }
298
+
299
+ readMetaMap() {
300
+ return this.implReadMap(MetaMap);
301
+ }
302
+
303
+ readMap() {
304
+ return this.implReadMap(ShvMap);
305
+ }
306
+
307
+ readIMap() {
308
+ return this.implReadMap(IMap);
309
+ }
310
+
311
+ private implReadMap<MapType extends MetaMap | ShvMap | IMap>(MapTypeCtor: new () => MapType) {
312
+ const map = new MapTypeCtor();
313
+ while (true) {
314
+ const b = this.ctx.peekByte();
315
+ if (b as PackingSchema === PackingSchema.Term) {
316
+ this.ctx.getByte();
317
+ break;
318
+ }
319
+ const key = this.read();
320
+ const val = this.read();
321
+ if (map instanceof MetaMap && typeof key === 'string') {
322
+ map.value[key] = val;
323
+ } else if (map instanceof MetaMap && key instanceof Int) {
324
+ map.value[Number(key)] = val;
325
+ } else if (map instanceof ShvMap && typeof key === 'string') {
326
+ map.value[key] = val;
327
+ } else if (map instanceof IMap && key instanceof Int) {
328
+ map.value[Number(key)] = val;
329
+ } else {
330
+ throw new TypeError('Malformed map, invalid key');
331
+ }
332
+ }
333
+ return map;
334
+ }
335
+ }
336
+
337
+ class ChainPackWriter {
338
+ ctx: PackContext;
339
+
340
+ constructor() {
341
+ this.ctx = new PackContext();
342
+ }
343
+
344
+ write(rpc_val: RpcValue) {
345
+ if (rpc_val instanceof RpcValueWithMetaData) {
346
+ this.writeMeta(rpc_val.meta);
347
+ rpc_val = rpc_val.value;
348
+ }
349
+ switch (true) {
350
+ case rpc_val === undefined:
351
+ this.ctx.putByte(PackingSchema.Null);
352
+ break;
353
+ case typeof rpc_val === 'boolean':
354
+ this.ctx.putByte(rpc_val ? PackingSchema.True : PackingSchema.False);
355
+ break;
356
+ case typeof rpc_val === 'string':
357
+ this.writeJSString(rpc_val);
358
+ break;
359
+ case rpc_val instanceof ArrayBuffer:
360
+ this.writeBlob(rpc_val);
361
+ break;
362
+ case rpc_val instanceof UInt:
363
+ this.writeUInt(rpc_val);
364
+ break;
365
+ case rpc_val instanceof Int:
366
+ this.writeInt(rpc_val);
367
+ break;
368
+ case rpc_val instanceof Decimal:
369
+ this.writeDecimal(rpc_val);
370
+ break;
371
+ case Array.isArray(rpc_val):
372
+ this.writeList(rpc_val);
373
+ break;
374
+ case rpc_val instanceof IMap:
375
+ this.writeIMap(rpc_val);
376
+ break;
377
+ case rpc_val instanceof ShvMap:
378
+ this.writeMap(rpc_val);
379
+ break;
380
+ case rpc_val instanceof Date:
381
+ this.writeDateTime(rpc_val);
382
+ break;
383
+ default:
384
+ console.log('Can\'t serialize', rpc_val);
385
+ throw new Error('Can\'t serialize');
386
+ }
387
+ }
388
+
389
+ writeUIntDataHelper(num: bigint, type: 'Int' | 'UInt') {
390
+ const is_negative = num < 0;
391
+ if (is_negative) {
392
+ num = -num;
393
+ }
394
+ const significant_bits = significant_bits_count(num) + (/* one more needed for sign bit */ type === 'Int' ? 1 : 0);
395
+ const bytes_needed = significant_bits <= 28 ? Math.trunc((significant_bits - 1) / 7) + 1 : Math.trunc((significant_bits - 1) / 8) + 2;
396
+
397
+ switch (bytes_needed) {
398
+ case 0:
399
+ throw new Error(`Failed to count bytes needed for ${num}`);
400
+ case 1:
401
+ if (is_negative) {
402
+ num |= 0b0100_0000n;
403
+ }
404
+ break;
405
+ case 2:
406
+ num |= 0b1000_0000n << 8n;
407
+ if (is_negative) {
408
+ num |= 0b0010_0000n << 8n;
409
+ }
410
+ break;
411
+ case 3:
412
+ num |= 0b1100_0000n << 16n;
413
+ if (is_negative) {
414
+ num |= 0b0001_0000n << 16n;
415
+ }
416
+ break;
417
+ case 4:
418
+ num |= 0b1110_0000n << 24n;
419
+ if (is_negative) {
420
+ num |= 0b0000_1000n << 24n;
421
+ }
422
+ break;
423
+ default:
424
+ num |= 0b1111_0000n << BigInt((bytes_needed - 1) * 8);
425
+ num |= BigInt(bytes_needed - 4 /* n is offset by 4 */ - 1 /* for the control byte */) << BigInt((bytes_needed - 1) * 8);
426
+ if (is_negative) {
427
+ num |= 0b1000_0000n << BigInt((bytes_needed - 2) * 8);
428
+ }
429
+ break;
430
+ }
431
+
432
+ const bytes = number_to_uint8_array(num);
433
+ for (const byte of bytes) {
434
+ this.ctx.putByte(byte);
435
+ }
436
+ }
437
+
438
+ writeUIntData(num: number) {
439
+ this.writeUIntDataHelper(BigInt(num), 'UInt');
440
+ }
441
+
442
+ writeIntData(snum: number) {
443
+ this.writeUIntDataHelper(BigInt(snum), 'Int');
444
+ }
445
+
446
+ writeInt(n: Int) {
447
+ if (Number(n) >= 0 && Number(n) < 64) {
448
+ this.ctx.putByte(Number(n) + 64);
449
+ return;
450
+ }
451
+
452
+ this.ctx.putByte(PackingSchema.Int);
453
+ this.writeIntData(Number(n));
454
+ }
455
+
456
+ writeUInt(n: UInt) {
457
+ if (Number(n) < 64) {
458
+ this.ctx.putByte(Number(n));
459
+ return;
460
+ }
461
+
462
+ this.ctx.putByte(PackingSchema.UInt);
463
+ this.writeUIntData(Number(n));
464
+ }
465
+
466
+ writeJSString(str: string) {
467
+ this.ctx.putByte(PackingSchema.String);
468
+ const pctx = new PackContext();
469
+ pctx.writeStringUtf8(str);
470
+ this.writeUIntData(pctx.length);
471
+ for (let i = 0; i < pctx.length; i++) {
472
+ this.ctx.putByte(pctx.data[i]);
473
+ }
474
+ }
475
+
476
+ writeDecimal(val: Decimal) {
477
+ this.ctx.putByte(PackingSchema.Decimal);
478
+ this.writeIntData(val.mantisa);
479
+ this.writeIntData(val.exponent);
480
+ }
481
+
482
+ writeBlob(blob: ArrayBuffer) {
483
+ this.ctx.putByte(PackingSchema.Blob);
484
+ const arr = new Uint8Array(blob);
485
+ this.writeUIntData(arr.length);
486
+ for (const element of arr) {
487
+ this.ctx.putByte(element);
488
+ }
489
+ }
490
+
491
+ writeDateTime(dt: DateTime) {
492
+ this.ctx.putByte(PackingSchema.DateTime);
493
+
494
+ let msecs = (dt.getTime() + (60_000 * (dt.utc_offset ?? 0))) - SHV_EPOCH_MSEC;
495
+ if (msecs < 0) {
496
+ throw new RangeError('DateTime prior to 2018-02-02 are not supported in current ChainPack implementation.');
497
+ }
498
+
499
+ const ms = msecs % 1000;
500
+ if (ms === 0) {
501
+ msecs /= 1000;
502
+ }
503
+
504
+ let bi = BigInt(msecs);
505
+ if (dt.utc_offset !== undefined) {
506
+ bi <<= 7n;
507
+ bi |= BigInt((dt.utc_offset / 15) & 0x7F);
508
+ }
509
+
510
+ bi <<= 2n;
511
+
512
+ if (dt.utc_offset !== undefined) {
513
+ bi |= 1n;
514
+ }
515
+
516
+ if (ms === 0) {
517
+ bi |= 2n;
518
+ }
519
+
520
+ // save as signed int
521
+ this.writeUIntDataHelper(bi, 'UInt');
522
+ }
523
+
524
+ writeList(lst: RpcValue[]) {
525
+ this.ctx.putByte(PackingSchema.List);
526
+ for (const element of lst) {
527
+ this.write(element);
528
+ }
529
+ this.ctx.putByte(PackingSchema.Term);
530
+ }
531
+
532
+ writeMeta(map: MetaMap) {
533
+ this.ctx.putByte(PackingSchema.MetaMap);
534
+ this.writeMapContent(map);
535
+ }
536
+
537
+ writeIMap(map: IMap) {
538
+ this.ctx.putByte(PackingSchema.IMap);
539
+ this.writeMapContent(map);
540
+ }
541
+
542
+ writeMap(map: ShvMap) {
543
+ this.ctx.putByte(PackingSchema.Map);
544
+ this.writeMapContent(map);
545
+ }
546
+
547
+ writeMapContent(map: MetaMap | ShvMap | IMap) {
548
+ for (const [key, value] of Object.entries(map.value)) {
549
+ if (value === undefined) {
550
+ continue;
551
+ }
552
+
553
+ if (map instanceof IMap) {
554
+ const int_key = Number(key);
555
+ if (Number.isNaN(int_key)) {
556
+ throw new TypeError('Invalid NaN IMap key');
557
+ }
558
+
559
+ this.writeInt(new Int(int_key));
560
+ } else if (map instanceof MetaMap) {
561
+ const int_key = Number(key);
562
+ if (Number.isNaN(int_key)) {
563
+ this.writeJSString(key.toString());
564
+ } else {
565
+ this.writeInt(new Int(int_key));
566
+ }
567
+ } else {
568
+ this.writeJSString(key.toString());
569
+ }
570
+ this.write(value);
571
+ }
572
+
573
+ this.ctx.putByte(PackingSchema.Term);
574
+ }
575
+ }
576
+
577
+ const toChainPack = (value: RpcValue) => {
578
+ const wr = new ChainPackWriter();
579
+ wr.write(value);
580
+ return wr.ctx.buffer();
581
+ };
582
+
583
+ const fromChainPack = (buffer: ArrayBuffer) => {
584
+ const reader = new ChainPackReader(buffer);
585
+ return reader.read();
586
+ };
587
+
588
+ const ChainpackProtocolType = 1;
589
+
590
+ export {toChainPack, fromChainPack, ChainPackReader, ChainPackWriter, ChainpackProtocolType};
@@ -0,0 +1,83 @@
1
+ class UnpackContext {
2
+ index: number;
3
+ private readonly data: Uint8Array;
4
+
5
+ constructor(buf: ArrayBuffer) {
6
+ this.data = new Uint8Array(buf);
7
+ this.index = 0;
8
+ }
9
+
10
+ getByte() {
11
+ if (this.index >= this.data.length) {
12
+ throw new RangeError('unexpected end of data');
13
+ }
14
+ return this.data[this.index++];
15
+ }
16
+
17
+ peekByte() {
18
+ if (this.index >= this.data.length) {
19
+ return -1;
20
+ }
21
+ return this.data[this.index];
22
+ }
23
+
24
+ getBytes(str: string) {
25
+ for (let i = 0; i < str.length; i++) {
26
+ if (str.codePointAt(i) !== this.getByte()) {
27
+ throw new TypeError(`'${str}' expected`);
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ const transfer = (source: ArrayBuffer, length: number) => {
34
+ if (length <= source.byteLength) {
35
+ return source.slice(0, length);
36
+ }
37
+
38
+ const source_view = new Uint8Array(source);
39
+ const dest_view = new Uint8Array(new ArrayBuffer(length));
40
+ dest_view.set(source_view);
41
+ return dest_view.buffer;
42
+ };
43
+
44
+ class PackContext {
45
+ static CHUNK_LEN = 1024;
46
+
47
+ data = new Uint8Array(0);
48
+ length = 0;
49
+
50
+ putByte(b: number) {
51
+ if (this.length >= this.data.length) {
52
+ const buffer = transfer(this.data.buffer, this.data.length + PackContext.CHUNK_LEN);
53
+ this.data = new Uint8Array(buffer);
54
+ }
55
+ this.data[this.length++] = b;
56
+ }
57
+
58
+ writeStringUtf8(str: string) {
59
+ for (let i = 0; i < str.length; i++) {
60
+ const charcode = str.codePointAt(i)!;
61
+ this.writeCharCodeUtf8(charcode);
62
+ }
63
+ }
64
+
65
+ writeCharCodeUtf8(charcode: number) {
66
+ if (charcode < 0x80) {
67
+ this.putByte(charcode);
68
+ } else if (charcode < 0x8_00) {
69
+ this.putByte(0xC0 | (charcode >> 6));
70
+ this.putByte(0x80 | (charcode & 0x3F));
71
+ } else if (charcode < 0xD8_00 || charcode >= 0xE0_00) {
72
+ this.putByte(0xE0 | (charcode >> 12));
73
+ this.putByte(0x80 | ((charcode >> 6) & 0x3F));
74
+ this.putByte(0x80 | (charcode & 0x3F));
75
+ }
76
+ }
77
+
78
+ buffer() {
79
+ return this.data.buffer.slice(0, this.length);
80
+ }
81
+ }
82
+
83
+ export {PackContext, UnpackContext};