UNPKG

20 kBJavaScriptView Raw
1import isNode from 'detect-node';
2import { is, isNil, type as rType } from 'ramda';
3import { bigint, bigStringInt, uintToInt, intToUint, bytesToHex, gzipUncompress, bytesToArrayBuffer } from './bin';
4
5const toUint32 = buf => {
6 let ln, res;
7 if (!isNode) //TODO browser behavior not equals, why?
8 return new Uint32Array(buf);
9 if (buf.readUInt32LE) {
10 ln = buf.byteLength / 4;
11 res = new Uint32Array(ln);
12 for (let i = 0; i < ln; i++) res[i] = buf.readUInt32LE(i * 4);
13 } else {
14 const data = new DataView(buf);
15 ln = data.byteLength / 4;
16 res = new Uint32Array(ln);
17 for (let i = 0; i < ln; i++) res[i] = data.getUint32(i * 4, true);
18 }
19 return res;
20};
21
22export const TL = (api, mtApi) => {
23
24 class Serialization {
25 constructor({ mtproto = false, startMaxLength = 2048 /* 2Kb */ } = {}) {
26 this.storeIntString = (value, field) => {
27 const valType = rType(value);
28 switch (true) {
29 case is(String, value):
30 return this.storeString(value, field);
31 case is(Number, value):
32 return this.storeInt(value, field);
33 default:
34 throw new Error(`tl storeIntString field ${field} value type ${valType}`);
35 }
36 };
37
38 this.storeInt = (i, field = '') => {
39 this.writeInt(i, `${field}:int`);
40 };
41
42 this.maxLength = startMaxLength;
43 this.offset = 0; // in bytes
44
45 this.createBuffer();
46 this.mtproto = mtproto;
47 }
48
49 createBuffer() {
50 this.buffer = new ArrayBuffer(this.maxLength);
51 this.intView = new Int32Array(this.buffer);
52 this.byteView = new Uint8Array(this.buffer);
53 }
54
55 getArray() {
56 const resultBuffer = new ArrayBuffer(this.offset);
57 const resultArray = new Int32Array(resultBuffer);
58
59 resultArray.set(this.intView.subarray(0, this.offset / 4));
60
61 return resultArray;
62 }
63
64 getBuffer() {
65 return this.getArray().buffer;
66 }
67
68 getBytes(typed) {
69 if (typed) {
70 const resultBuffer = new ArrayBuffer(this.offset);
71 const resultArray = new Uint8Array(resultBuffer);
72
73 resultArray.set(this.byteView.subarray(0, this.offset));
74
75 return resultArray;
76 }
77
78 const bytes = [];
79 for (let i = 0; i < this.offset; i++) {
80 bytes.push(this.byteView[i]);
81 }
82 return bytes;
83 }
84
85 checkLength(needBytes) {
86 if (this.offset + needBytes < this.maxLength) {
87 return;
88 }
89
90 console.trace('Increase buffer', this.offset, needBytes, this.maxLength);
91 this.maxLength = Math.ceil(Math.max(this.maxLength * 2, this.offset + needBytes + 16) / 4) * 4;
92 const previousBuffer = this.buffer;
93 const previousArray = new Int32Array(previousBuffer);
94
95 this.createBuffer();
96
97 new Int32Array(this.buffer).set(previousArray);
98 }
99
100 writeInt(i, field) {
101 this.debug && console.log('>>>', i.toString(16), i, field);
102
103 this.checkLength(4);
104 this.intView[this.offset / 4] = i;
105 this.offset += 4;
106 }
107
108 storeBool(i, field = '') {
109 if (i) {
110 this.writeInt(0x997275b5, `${field}:bool`);
111 } else {
112 this.writeInt(0xbc799737, `${field}:bool`);
113 }
114 }
115
116 storeLongP(iHigh, iLow, field) {
117 this.writeInt(iLow, `${field}:long[low]`);
118 this.writeInt(iHigh, `${field}:long[high]`);
119 }
120
121 storeLong(sLong, field = '') {
122 if (is(Array, sLong)) return sLong.length === 2 ? this.storeLongP(sLong[0], sLong[1], field) : this.storeIntBytes(sLong, 64, field);
123
124 if (typeof sLong !== 'string') sLong = sLong ? sLong.toString() : '0';
125 const divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
126
127 this.writeInt(intToUint(divRem[1].intValue()), `${field}:long[low]`);
128 this.writeInt(intToUint(divRem[0].intValue()), `${field}:long[high]`);
129 }
130
131 storeDouble(f, field = '') {
132 const buffer = new ArrayBuffer(8);
133 const intView = new Int32Array(buffer);
134 const doubleView = new Float64Array(buffer);
135
136 doubleView[0] = f;
137
138 this.writeInt(intView[0], `${field}:double[low]`);
139 this.writeInt(intView[1], `${field}:double[high]`);
140 }
141
142 storeString(s, field = '') {
143 this.debug && console.log('>>>', s, `${field}:string`);
144
145 if (s === undefined) {
146 s = '';
147 }
148 const sUTF8 = unescape(encodeURIComponent(s));
149
150 this.checkLength(sUTF8.length + 8);
151
152 const len = sUTF8.length;
153 if (len <= 253) {
154 this.byteView[this.offset++] = len;
155 } else {
156 this.byteView[this.offset++] = 254;
157 this.byteView[this.offset++] = len & 0xFF;
158 this.byteView[this.offset++] = (len & 0xFF00) >> 8;
159 this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
160 }
161 for (let i = 0; i < len; i++) {
162 this.byteView[this.offset++] = sUTF8.charCodeAt(i);
163 }
164
165 // Padding
166 while (this.offset % 4) {
167 this.byteView[this.offset++] = 0;
168 }
169 }
170
171 storeBytes(bytes, field = '') {
172 if (bytes instanceof ArrayBuffer) {
173 bytes = new Uint8Array(bytes);
174 } else if (bytes === undefined) {
175 bytes = [];
176 }
177 this.debug && console.log('>>>', bytesToHex(bytes), `${field}:bytes`);
178
179 const len = bytes.byteLength || bytes.length;
180 this.checkLength(len + 8);
181 if (len <= 253) {
182 this.byteView[this.offset++] = len;
183 } else {
184 this.byteView[this.offset++] = 254;
185 this.byteView[this.offset++] = len & 0xFF;
186 this.byteView[this.offset++] = (len & 0xFF00) >> 8;
187 this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
188 }
189
190 this.byteView.set(bytes, this.offset);
191 this.offset += len;
192
193 // Padding
194 while (this.offset % 4) {
195 this.byteView[this.offset++] = 0;
196 }
197 }
198
199 storeIntBytes(bytes, bits, field = '') {
200 if (bytes instanceof ArrayBuffer) {
201 bytes = new Uint8Array(bytes);
202 }
203 const len = bytes.length;
204 if (bits % 32 || len * 8 != bits) {
205 throw new Error(`Invalid bits: ${bits}, ${bytes.length}`);
206 }
207
208 this.debug && console.log('>>>', bytesToHex(bytes), `${field}:int${bits}`);
209 this.checkLength(len);
210
211 this.byteView.set(bytes, this.offset);
212 this.offset += len;
213 }
214
215 storeRawBytes(bytes, field = '') {
216 if (bytes instanceof ArrayBuffer) {
217 bytes = new Uint8Array(bytes);
218 }
219 const len = bytes.length;
220
221 this.debug && console.log('>>>', bytesToHex(bytes), field);
222 this.checkLength(len);
223
224 this.byteView.set(bytes, this.offset);
225 this.offset += len;
226 }
227
228 storeMethod(methodName, params) {
229 const schema = this.mtproto ? mtApi : api;
230 let methodData = false;
231
232 for (let i = 0; i < schema.methods.length; i++) {
233 if (schema.methods[i].method == methodName) {
234 methodData = schema.methods[i];
235 break;
236 }
237 }
238 if (!methodData) {
239 throw new Error(`No method ${methodName} found`);
240 }
241
242 this.storeInt(intToUint(methodData.id), `${methodName}[id]`);
243
244 let param, type;
245 let condType;
246 let fieldBit;
247 const len = methodData.params.length;
248 for (let i = 0; i < len; i++) {
249 param = methodData.params[i];
250 type = param.type;
251 if (type.indexOf('?') !== -1) {
252 condType = type.split('?');
253 fieldBit = condType[0].split('.');
254 if (!(params[fieldBit[0]] & 1 << fieldBit[1])) {
255 continue;
256 }
257 type = condType[1];
258 }
259
260 this.storeObject(params[param.name], type, `${methodName}[${param.name}]`);
261 }
262
263 return methodData.type;
264 }
265
266 storeObject(obj, type, field) {
267 switch (type) {
268 case '#':
269 case 'int':
270 return this.storeInt(obj, field);
271 case 'long':
272 return this.storeLong(obj, field);
273 case 'int128':
274 return this.storeIntBytes(obj, 128, field);
275 case 'int256':
276 return this.storeIntBytes(obj, 256, field);
277 case 'int512':
278 return this.storeIntBytes(obj, 512, field);
279 case 'string':
280 return this.storeString(obj, field);
281 case 'bytes':
282 return this.storeBytes(obj, field);
283 case 'double':
284 return this.storeDouble(obj, field);
285 case 'Bool':
286 return this.storeBool(obj, field);
287 case 'true':
288 return;
289 }
290
291 if (is(Array, obj)) {
292 if (type.substr(0, 6) == 'Vector') {
293 this.writeInt(0x1cb5c415, `${field}[id]`);
294 } else if (type.substr(0, 6) != 'vector') {
295 throw new Error(`Invalid vector type ${type}`);
296 }
297 const itemType = type.substr(7, type.length - 8); // for "Vector<itemType>"
298 this.writeInt(obj.length, `${field}[count]`);
299 for (let i = 0; i < obj.length; i++) {
300 this.storeObject(obj[i], itemType, `${field}[${i}]`);
301 }
302 return true;
303 } else if (type.substr(0, 6).toLowerCase() == 'vector') {
304 throw new Error('Invalid vector object');
305 }
306
307 if (!is(Object, obj)) throw new Error(`Invalid object for type ${type}`);
308
309 const schema = this.mtproto ? mtApi : api;
310 const predicate = obj['_'];
311 let isBare = false;
312 let constructorData = false;
313
314 if (isBare = type.charAt(0) == '%') type = type.substr(1);
315
316 for (let i = 0; i < schema.constructors.length; i++) {
317 if (schema.constructors[i].predicate == predicate) {
318 constructorData = schema.constructors[i];
319 break;
320 }
321 }
322 if (!constructorData) throw new Error(`No predicate ${predicate} found`);
323
324 if (predicate == type) isBare = true;
325
326 if (!isBare) this.writeInt(intToUint(constructorData.id), `${field}[${predicate}][id]`);
327
328 let param;
329 let condType;
330 let fieldBit;
331 const len = constructorData.params.length;
332 for (let i = 0; i < len; i++) {
333 param = constructorData.params[i];
334 type = param.type;
335 if (type.indexOf('?') !== -1) {
336 condType = type.split('?');
337 fieldBit = condType[0].split('.');
338 if (!(obj[fieldBit[0]] & 1 << fieldBit[1])) {
339 continue;
340 }
341 type = condType[1];
342 }
343
344 this.storeObject(obj[param.name], type, `${field}[${predicate}][${param.name}]`);
345 }
346
347 return constructorData.type;
348 }
349
350 }
351
352 class Deserialization {
353 constructor(buffer, { mtproto = false, override = {} } = {}) {
354 this.offset = 0; // in bytes
355 this.override = override;
356
357 this.buffer = buffer;
358 this.intView = toUint32(this.buffer);
359 this.byteView = new Uint8Array(this.buffer);
360 this.mtproto = mtproto;
361 }
362
363 readInt(field) {
364 if (this.offset >= this.intView.length * 4) {
365 throw new Error(`Nothing to fetch: ${field}`);
366 }
367
368 const i = this.intView[this.offset / 4];
369
370 this.debug && console.log('<<<', i.toString(16), i, field);
371
372 this.offset += 4;
373
374 return i;
375 }
376
377 fetchInt(field = '') {
378 return this.readInt(`${field}:int`);
379 }
380
381 fetchDouble(field = '') {
382 const buffer = new ArrayBuffer(8);
383 const intView = new Int32Array(buffer);
384 const doubleView = new Float64Array(buffer);
385
386 intView[0] = this.readInt(`${field}:double[low]`), intView[1] = this.readInt(`${field}:double[high]`);
387
388 return doubleView[0];
389 }
390
391 fetchLong(field = '') {
392 const iLow = this.readInt(`${field}:long[low]`);
393 const iHigh = this.readInt(`${field}:long[high]`);
394
395 const longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString();
396
397 return longDec;
398 }
399
400 fetchBool(field = '') {
401 const i = this.readInt(`${field}:bool`);
402 if (i == 0x997275b5) {
403 return true;
404 } else if (i == 0xbc799737) {
405 return false;
406 }
407
408 this.offset -= 4;
409 return this.fetchObject('Object', field);
410 }
411
412 fetchString(field = '') {
413 let len = this.byteView[this.offset++];
414
415 if (len == 254) {
416 len = this.byteView[this.offset++] | this.byteView[this.offset++] << 8 | this.byteView[this.offset++] << 16;
417 }
418
419 let sUTF8 = '';
420 for (let i = 0; i < len; i++) {
421 sUTF8 += String.fromCharCode(this.byteView[this.offset++]);
422 }
423
424 // Padding
425 while (this.offset % 4) {
426 this.offset++;
427 }
428 let s;
429 try {
430 s = decodeURIComponent(escape(sUTF8));
431 } catch (e) {
432 s = sUTF8;
433 }
434
435 this.debug && console.log('<<<', s, `${field}:string`);
436
437 return s;
438 }
439
440 fetchBytes(field = '') {
441 let len = this.byteView[this.offset++];
442
443 if (len == 254) {
444 len = this.byteView[this.offset++] | this.byteView[this.offset++] << 8 | this.byteView[this.offset++] << 16;
445 }
446
447 const bytes = this.byteView.subarray(this.offset, this.offset + len);
448 this.offset += len;
449
450 // Padding
451 while (this.offset % 4) this.offset++;
452
453 this.debug && console.log('<<<', bytesToHex(bytes), `${field}:bytes`);
454
455 return bytes;
456 }
457
458 fetchIntBytes(bits, typed, field = '') {
459 if (bits % 32) throw new Error(`Invalid bits: ${bits}`);
460
461 const len = bits / 8;
462 if (typed) {
463 const result = this.byteView.subarray(this.offset, this.offset + len);
464 this.offset += len;
465 return result;
466 }
467
468 const bytes = [];
469 for (let i = 0; i < len; i++) {
470 bytes.push(this.byteView[this.offset++]);
471 }
472
473 this.debug && console.log('<<<', bytesToHex(bytes), `${field}:int${bits}`);
474
475 return bytes;
476 }
477
478 fetchRawBytes(len, typed, field = '') {
479 if (len === false) {
480 len = this.readInt(`${field}_length`);
481 if (len > this.byteView.byteLength) throw new Error(`Invalid raw bytes length: ${len}, buffer len: ${this.byteView.byteLength}`);
482 }
483 let bytes;
484 if (typed) {
485 bytes = new Uint8Array(len);
486 bytes.set(this.byteView.subarray(this.offset, this.offset + len));
487 this.offset += len;
488 return bytes;
489 }
490
491 bytes = [];
492 for (let i = 0; i < len; i++) bytes.push(this.byteView[this.offset++]);
493
494 this.debug && console.log('<<<', bytesToHex(bytes), field);
495
496 return bytes;
497 }
498
499 fetchObject(type, field) {
500 switch (type) {
501 case '#':
502 case 'int':
503 return this.fetchInt(field);
504 case 'long':
505 return this.fetchLong(field);
506 case 'int128':
507 return this.fetchIntBytes(128, false, field);
508 case 'int256':
509 return this.fetchIntBytes(256, false, field);
510 case 'int512':
511 return this.fetchIntBytes(512, false, field);
512 case 'string':
513 return this.fetchString(field);
514 case 'bytes':
515 return this.fetchBytes(field);
516 case 'double':
517 return this.fetchDouble(field);
518 case 'Bool':
519 return this.fetchBool(field);
520 case 'true':
521 return true;
522 }
523 let fallback;
524 field = field || type || 'Object';
525 const subpart = type.substr(0, 6);
526 if (subpart === 'Vector' || subpart === 'vector') {
527 if (type.charAt(0) === 'V') {
528 const constructor = this.readInt(`${field}[id]`);
529 const constructorCmp = uintToInt(constructor);
530
531 if (constructorCmp === 0x3072cfa1) {
532 // Gzip packed
533 const compressed = this.fetchBytes(`${field}[packed_string]`);
534 const uncompressed = gzipUncompress(compressed);
535 const buffer = bytesToArrayBuffer(uncompressed);
536 const newDeserializer = new Deserialization(buffer);
537
538 return newDeserializer.fetchObject(type, field);
539 }
540 if (constructorCmp !== 0x1cb5c415) throw new Error(`Invalid vector constructor ${constructor}`);
541 }
542 const len = this.readInt(`${field}[count]`);
543 const result = [];
544 if (len > 0) {
545 const itemType = type.substr(7, type.length - 8); // for "Vector<itemType>"
546 for (let i = 0; i < len; i++) result.push(this.fetchObject(itemType, `${field}[${i}]`));
547 }
548
549 return result;
550 }
551
552 const schema = this.mtproto ? mtApi : api;
553 let predicate = false;
554 let constructorData = false;
555
556 if (type.charAt(0) == '%') {
557 const checkType = type.substr(1);
558 for (let i = 0; i < schema.constructors.length; i++) {
559 if (schema.constructors[i].type == checkType) {
560 constructorData = schema.constructors[i];
561 break;
562 }
563 }
564 if (!constructorData) {
565 throw new Error(`Constructor not found for type: ${type}`);
566 }
567 } else if (type.charAt(0) >= 97 && type.charAt(0) <= 122) {
568 for (let i = 0; i < schema.constructors.length; i++) {
569 if (schema.constructors[i].predicate == type) {
570 constructorData = schema.constructors[i];
571 break;
572 }
573 }
574 if (!constructorData) throw new Error(`Constructor not found for predicate: ${type}`);
575 } else {
576 const constructor = this.readInt(`${field}[id]`);
577 const constructorCmp = uintToInt(constructor);
578
579 if (constructorCmp == 0x3072cfa1) {
580 // Gzip packed
581 const compressed = this.fetchBytes(`${field}[packed_string]`);
582 const uncompressed = gzipUncompress(compressed);
583 const buffer = bytesToArrayBuffer(uncompressed);
584 const newDeserializer = new Deserialization(buffer);
585
586 return newDeserializer.fetchObject(type, field);
587 }
588
589 let index = schema.constructorsIndex;
590 if (!index) {
591 schema.constructorsIndex = index = {};
592 for (let i = 0; i < schema.constructors.length; i++) index[schema.constructors[i].id] = i;
593 }
594 let i = index[constructorCmp];
595 if (i) constructorData = schema.constructors[i];
596
597 fallback = false;
598 if (!constructorData && this.mtproto) {
599 const schemaFallback = api;
600 for (i = 0; i < schemaFallback.constructors.length; i++) {
601 if (schemaFallback.constructors[i].id == constructorCmp) {
602 constructorData = schemaFallback.constructors[i];
603
604 delete this.mtproto;
605 fallback = true;
606 break;
607 }
608 }
609 }
610 if (!constructorData) {
611 throw new Error(`Constructor not found: ${constructor} ${this.fetchInt()} ${this.fetchInt()}`);
612 }
613 }
614
615 predicate = constructorData.predicate;
616
617 const result = { '_': predicate };
618 const overrideKey = (this.mtproto ? 'mt_' : '') + predicate;
619
620 if (this.override[overrideKey]) {
621 this.override[overrideKey].apply(this, [result, `${field}[${predicate}]`]);
622 } else {
623 let param, isCond;
624 let condType, fieldBit;
625 let value;
626 const len = constructorData.params.length;
627 for (let i = 0; i < len; i++) {
628 param = constructorData.params[i];
629 type = param.type;
630 if (type === '#' && isNil(result.pFlags)) result.pFlags = {};
631
632 isCond = type.indexOf('?') !== -1;
633 if (isCond) {
634 condType = type.split('?');
635 fieldBit = condType[0].split('.');
636 if (!(result[fieldBit[0]] & 1 << fieldBit[1])) continue;
637 type = condType[1];
638 }
639
640 value = this.fetchObject(type, `${field}[${predicate}][${param.name}]`);
641
642 if (isCond && type === 'true') result.pFlags[param.name] = value;else result[param.name] = value;
643 }
644 }
645
646 if (fallback) this.mtproto = true;
647
648 return result;
649 }
650
651 getOffset() {
652 return this.offset;
653 }
654
655 fetchEnd() {
656 if (this.offset !== this.byteView.length) throw new Error('Fetch end with non-empty buffer');
657 return true;
658 }
659
660 }
661
662 return { Serialization, Deserialization };
663};
664
665export default TL;
666//# sourceMappingURL=tl.js.map
\No newline at end of file