UNPKG

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