UNPKG

20.4 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 divRem = (0, _bin.bigStringInt)(sLong).divideAndRemainder((0, _bin.bigint)(0x100000000));
139
140 this.writeInt((0, _bin.intToUint)(divRem[1].intValue()), `${field}:long[low]`);
141 this.writeInt((0, _bin.intToUint)(divRem[0].intValue()), `${field}:long[high]`);
142 }
143
144 storeDouble(f, field = '') {
145 const buffer = new ArrayBuffer(8);
146 const intView = new Int32Array(buffer);
147 const doubleView = new Float64Array(buffer);
148
149 doubleView[0] = f;
150
151 this.writeInt(intView[0], `${field}:double[low]`);
152 this.writeInt(intView[1], `${field}:double[high]`);
153 }
154
155 storeString(s, field = '') {
156 this.debug && console.log('>>>', s, `${field}:string`);
157
158 if (s === undefined) {
159 s = '';
160 }
161 const sUTF8 = unescape(encodeURIComponent(s));
162
163 this.checkLength(sUTF8.length + 8);
164
165 const len = sUTF8.length;
166 if (len <= 253) {
167 this.byteView[this.offset++] = len;
168 } else {
169 this.byteView[this.offset++] = 254;
170 this.byteView[this.offset++] = len & 0xFF;
171 this.byteView[this.offset++] = (len & 0xFF00) >> 8;
172 this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
173 }
174 for (let i = 0; i < len; i++) {
175 this.byteView[this.offset++] = sUTF8.charCodeAt(i);
176 }
177
178 // Padding
179 while (this.offset % 4) {
180 this.byteView[this.offset++] = 0;
181 }
182 }
183
184 storeBytes(bytes, field = '') {
185 if (bytes instanceof ArrayBuffer) {
186 bytes = new Uint8Array(bytes);
187 } else if (bytes === undefined) {
188 bytes = [];
189 }
190 this.debug && console.log('>>>', (0, _bin.bytesToHex)(bytes), `${field}:bytes`);
191
192 const len = bytes.byteLength || bytes.length;
193 this.checkLength(len + 8);
194 if (len <= 253) {
195 this.byteView[this.offset++] = len;
196 } else {
197 this.byteView[this.offset++] = 254;
198 this.byteView[this.offset++] = len & 0xFF;
199 this.byteView[this.offset++] = (len & 0xFF00) >> 8;
200 this.byteView[this.offset++] = (len & 0xFF0000) >> 16;
201 }
202
203 this.byteView.set(bytes, this.offset);
204 this.offset += len;
205
206 // Padding
207 while (this.offset % 4) {
208 this.byteView[this.offset++] = 0;
209 }
210 }
211
212 storeIntBytes(bytes, bits, field = '') {
213 if (bytes instanceof ArrayBuffer) {
214 bytes = new Uint8Array(bytes);
215 }
216 const len = bytes.length;
217 if (bits % 32 || len * 8 != bits) {
218 throw new Error(`Invalid bits: ${bits}, ${bytes.length}`);
219 }
220
221 this.debug && console.log('>>>', (0, _bin.bytesToHex)(bytes), `${field}:int${bits}`);
222 this.checkLength(len);
223
224 this.byteView.set(bytes, this.offset);
225 this.offset += len;
226 }
227
228 storeRawBytes(bytes, field = '') {
229 if (bytes instanceof ArrayBuffer) {
230 bytes = new Uint8Array(bytes);
231 }
232 const len = bytes.length;
233
234 this.debug && console.log('>>>', (0, _bin.bytesToHex)(bytes), field);
235 this.checkLength(len);
236
237 this.byteView.set(bytes, this.offset);
238 this.offset += len;
239 }
240
241 storeMethod(methodName, params) {
242 const schema = this.mtproto ? mtApi : api;
243 let methodData = false;
244
245 for (let i = 0; i < schema.methods.length; i++) {
246 if (schema.methods[i].method == methodName) {
247 methodData = schema.methods[i];
248 break;
249 }
250 }
251 if (!methodData) {
252 throw new Error(`No method ${methodName} found`);
253 }
254
255 this.storeInt((0, _bin.intToUint)(methodData.id), `${methodName}[id]`);
256
257 let param, type;
258 let condType;
259 let fieldBit;
260 const len = methodData.params.length;
261 for (let i = 0; i < len; i++) {
262 param = methodData.params[i];
263 type = param.type;
264 if (type.indexOf('?') !== -1) {
265 condType = type.split('?');
266 fieldBit = condType[0].split('.');
267 if (!(params[fieldBit[0]] & 1 << fieldBit[1])) {
268 continue;
269 }
270 type = condType[1];
271 }
272
273 this.storeObject(params[param.name], type, `${methodName}[${param.name}]`);
274 }
275
276 return methodData.type;
277 }
278
279 storeObject(obj, type, field) {
280 switch (type) {
281 case '#':
282 case 'int':
283 return this.storeInt(obj, field);
284 case 'long':
285 return this.storeLong(obj, field);
286 case 'int128':
287 return this.storeIntBytes(obj, 128, field);
288 case 'int256':
289 return this.storeIntBytes(obj, 256, field);
290 case 'int512':
291 return this.storeIntBytes(obj, 512, field);
292 case 'string':
293 return this.storeString(obj, field);
294 case 'bytes':
295 return this.storeBytes(obj, field);
296 case 'double':
297 return this.storeDouble(obj, field);
298 case 'Bool':
299 return this.storeBool(obj, field);
300 case 'true':
301 return;
302 }
303
304 if ((0, _ramda.is)(Array, obj)) {
305 if (type.substr(0, 6) == 'Vector') {
306 this.writeInt(0x1cb5c415, `${field}[id]`);
307 } else if (type.substr(0, 6) != 'vector') {
308 throw new Error(`Invalid vector type ${type}`);
309 }
310 const itemType = type.substr(7, type.length - 8); // for "Vector<itemType>"
311 this.writeInt(obj.length, `${field}[count]`);
312 for (let i = 0; i < obj.length; i++) {
313 this.storeObject(obj[i], itemType, `${field}[${i}]`);
314 }
315 return true;
316 } else if (type.substr(0, 6).toLowerCase() == 'vector') {
317 throw new Error('Invalid vector object');
318 }
319
320 if (!(0, _ramda.is)(Object, obj)) throw new Error(`Invalid object for type ${type}`);
321
322 const schema = this.mtproto ? mtApi : api;
323 const predicate = obj['_'];
324 let isBare = false;
325 let constructorData = false;
326
327 if (isBare = type.charAt(0) == '%') type = type.substr(1);
328
329 for (let i = 0; i < schema.constructors.length; i++) {
330 if (schema.constructors[i].predicate == predicate) {
331 constructorData = schema.constructors[i];
332 break;
333 }
334 }
335 if (!constructorData) throw new Error(`No predicate ${predicate} found`);
336
337 if (predicate == type) isBare = true;
338
339 if (!isBare) this.writeInt((0, _bin.intToUint)(constructorData.id), `${field}[${predicate}][id]`);
340
341 let param;
342 let condType;
343 let fieldBit;
344 const len = constructorData.params.length;
345 for (let i = 0; i < len; i++) {
346 param = constructorData.params[i];
347 type = param.type;
348 if (type.indexOf('?') !== -1) {
349 condType = type.split('?');
350 fieldBit = condType[0].split('.');
351 if (!(obj[fieldBit[0]] & 1 << fieldBit[1])) {
352 continue;
353 }
354 type = condType[1];
355 }
356
357 this.storeObject(obj[param.name], type, `${field}[${predicate}][${param.name}]`);
358 }
359
360 return constructorData.type;
361 }
362
363 }
364
365 class Deserialization {
366 constructor(buffer, { mtproto = false, override = {} } = {}) {
367 this.offset = 0; // in bytes
368 this.override = override;
369
370 this.buffer = buffer;
371 this.intView = toUint32(this.buffer);
372 this.byteView = new Uint8Array(this.buffer);
373 this.mtproto = mtproto;
374 }
375
376 readInt(field) {
377 if (this.offset >= this.intView.length * 4) {
378 throw new Error(`Nothing to fetch: ${field}`);
379 }
380
381 const i = this.intView[this.offset / 4];
382
383 this.debug && console.log('<<<', i.toString(16), i, field);
384
385 this.offset += 4;
386
387 return i;
388 }
389
390 fetchInt(field = '') {
391 return this.readInt(`${field}:int`);
392 }
393
394 fetchDouble(field = '') {
395 const buffer = new ArrayBuffer(8);
396 const intView = new Int32Array(buffer);
397 const doubleView = new Float64Array(buffer);
398
399 intView[0] = this.readInt(`${field}:double[low]`), intView[1] = this.readInt(`${field}:double[high]`);
400
401 return doubleView[0];
402 }
403
404 fetchLong(field = '') {
405 const iLow = this.readInt(`${field}:long[low]`);
406 const iHigh = this.readInt(`${field}:long[high]`);
407
408 const longDec = (0, _bin.bigint)(iHigh).shiftLeft(32).add((0, _bin.bigint)(iLow)).toString();
409
410 return longDec;
411 }
412
413 fetchBool(field = '') {
414 const i = this.readInt(`${field}:bool`);
415 if (i == 0x997275b5) {
416 return true;
417 } else if (i == 0xbc799737) {
418 return false;
419 }
420
421 this.offset -= 4;
422 return this.fetchObject('Object', field);
423 }
424
425 fetchString(field = '') {
426 let len = this.byteView[this.offset++];
427
428 if (len == 254) {
429 len = this.byteView[this.offset++] | this.byteView[this.offset++] << 8 | this.byteView[this.offset++] << 16;
430 }
431
432 let sUTF8 = '';
433 for (let i = 0; i < len; i++) {
434 sUTF8 += String.fromCharCode(this.byteView[this.offset++]);
435 }
436
437 // Padding
438 while (this.offset % 4) {
439 this.offset++;
440 }
441 let s;
442 try {
443 s = decodeURIComponent(escape(sUTF8));
444 } catch (e) {
445 s = sUTF8;
446 }
447
448 this.debug && console.log('<<<', s, `${field}:string`);
449
450 return s;
451 }
452
453 fetchBytes(field = '') {
454 let len = this.byteView[this.offset++];
455
456 if (len == 254) {
457 len = this.byteView[this.offset++] | this.byteView[this.offset++] << 8 | this.byteView[this.offset++] << 16;
458 }
459
460 const bytes = this.byteView.subarray(this.offset, this.offset + len);
461 this.offset += len;
462
463 // Padding
464 while (this.offset % 4) this.offset++;
465
466 this.debug && console.log('<<<', (0, _bin.bytesToHex)(bytes), `${field}:bytes`);
467
468 return bytes;
469 }
470
471 fetchIntBytes(bits, typed, field = '') {
472 if (bits % 32) throw new Error(`Invalid bits: ${bits}`);
473
474 const len = bits / 8;
475 if (typed) {
476 const result = this.byteView.subarray(this.offset, this.offset + len);
477 this.offset += len;
478 return result;
479 }
480
481 const bytes = [];
482 for (let i = 0; i < len; i++) {
483 bytes.push(this.byteView[this.offset++]);
484 }
485
486 this.debug && console.log('<<<', (0, _bin.bytesToHex)(bytes), `${field}:int${bits}`);
487
488 return bytes;
489 }
490
491 fetchRawBytes(len, typed, field = '') {
492 if (len === false) {
493 len = this.readInt(`${field}_length`);
494 if (len > this.byteView.byteLength) throw new Error(`Invalid raw bytes length: ${len}, buffer len: ${this.byteView.byteLength}`);
495 }
496 let bytes;
497 if (typed) {
498 bytes = new Uint8Array(len);
499 bytes.set(this.byteView.subarray(this.offset, this.offset + len));
500 this.offset += len;
501 return bytes;
502 }
503
504 bytes = [];
505 for (let i = 0; i < len; i++) bytes.push(this.byteView[this.offset++]);
506
507 this.debug && console.log('<<<', (0, _bin.bytesToHex)(bytes), field);
508
509 return bytes;
510 }
511
512 fetchObject(type, field) {
513 switch (type) {
514 case '#':
515 case 'int':
516 return this.fetchInt(field);
517 case 'long':
518 return this.fetchLong(field);
519 case 'int128':
520 return this.fetchIntBytes(128, false, field);
521 case 'int256':
522 return this.fetchIntBytes(256, false, field);
523 case 'int512':
524 return this.fetchIntBytes(512, false, field);
525 case 'string':
526 return this.fetchString(field);
527 case 'bytes':
528 return this.fetchBytes(field);
529 case 'double':
530 return this.fetchDouble(field);
531 case 'Bool':
532 return this.fetchBool(field);
533 case 'true':
534 return true;
535 }
536 let fallback;
537 field = field || type || 'Object';
538 const subpart = type.substr(0, 6);
539 if (subpart === 'Vector' || subpart === 'vector') {
540 if (type.charAt(0) === 'V') {
541 const constructor = this.readInt(`${field}[id]`);
542 const constructorCmp = (0, _bin.uintToInt)(constructor);
543
544 if (constructorCmp === 0x3072cfa1) {
545 // Gzip packed
546 const compressed = this.fetchBytes(`${field}[packed_string]`);
547 const uncompressed = (0, _bin.gzipUncompress)(compressed);
548 const buffer = (0, _bin.bytesToArrayBuffer)(uncompressed);
549 const newDeserializer = new Deserialization(buffer);
550
551 return newDeserializer.fetchObject(type, field);
552 }
553 if (constructorCmp !== 0x1cb5c415) throw new Error(`Invalid vector constructor ${constructor}`);
554 }
555 const len = this.readInt(`${field}[count]`);
556 const result = [];
557 if (len > 0) {
558 const itemType = type.substr(7, type.length - 8); // for "Vector<itemType>"
559 for (let i = 0; i < len; i++) result.push(this.fetchObject(itemType, `${field}[${i}]`));
560 }
561
562 return result;
563 }
564
565 const schema = this.mtproto ? mtApi : api;
566 let predicate = false;
567 let constructorData = false;
568
569 if (type.charAt(0) == '%') {
570 const checkType = type.substr(1);
571 for (let i = 0; i < schema.constructors.length; i++) {
572 if (schema.constructors[i].type == checkType) {
573 constructorData = schema.constructors[i];
574 break;
575 }
576 }
577 if (!constructorData) {
578 throw new Error(`Constructor not found for type: ${type}`);
579 }
580 } else if (type.charAt(0) >= 97 && type.charAt(0) <= 122) {
581 for (let i = 0; i < schema.constructors.length; i++) {
582 if (schema.constructors[i].predicate == type) {
583 constructorData = schema.constructors[i];
584 break;
585 }
586 }
587 if (!constructorData) throw new Error(`Constructor not found for predicate: ${type}`);
588 } else {
589 const constructor = this.readInt(`${field}[id]`);
590 const constructorCmp = (0, _bin.uintToInt)(constructor);
591
592 if (constructorCmp == 0x3072cfa1) {
593 // Gzip packed
594 const compressed = this.fetchBytes(`${field}[packed_string]`);
595 const uncompressed = (0, _bin.gzipUncompress)(compressed);
596 const buffer = (0, _bin.bytesToArrayBuffer)(uncompressed);
597 const newDeserializer = new Deserialization(buffer);
598
599 return newDeserializer.fetchObject(type, field);
600 }
601
602 let index = schema.constructorsIndex;
603 if (!index) {
604 schema.constructorsIndex = index = {};
605 for (let i = 0; i < schema.constructors.length; i++) index[schema.constructors[i].id] = i;
606 }
607 let i = index[constructorCmp];
608 if (i) constructorData = schema.constructors[i];
609
610 fallback = false;
611 if (!constructorData && this.mtproto) {
612 const schemaFallback = api;
613 for (i = 0; i < schemaFallback.constructors.length; i++) {
614 if (schemaFallback.constructors[i].id == constructorCmp) {
615 constructorData = schemaFallback.constructors[i];
616
617 delete this.mtproto;
618 fallback = true;
619 break;
620 }
621 }
622 }
623 if (!constructorData) {
624 throw new Error(`Constructor not found: ${constructor} ${this.fetchInt()} ${this.fetchInt()}`);
625 }
626 }
627
628 predicate = constructorData.predicate;
629
630 const result = { '_': predicate };
631 const overrideKey = (this.mtproto ? 'mt_' : '') + predicate;
632
633 if (this.override[overrideKey]) {
634 this.override[overrideKey].apply(this, [result, `${field}[${predicate}]`]);
635 } else {
636 let param, isCond;
637 let condType, fieldBit;
638 let value;
639 const len = constructorData.params.length;
640 for (let i = 0; i < len; i++) {
641 param = constructorData.params[i];
642 type = param.type;
643 if (type === '#' && (0, _ramda.isNil)(result.pFlags)) result.pFlags = {};
644
645 isCond = type.indexOf('?') !== -1;
646 if (isCond) {
647 condType = type.split('?');
648 fieldBit = condType[0].split('.');
649 if (!(result[fieldBit[0]] & 1 << fieldBit[1])) continue;
650 type = condType[1];
651 }
652
653 value = this.fetchObject(type, `${field}[${predicate}][${param.name}]`);
654
655 if (isCond && type === 'true') result.pFlags[param.name] = value;else result[param.name] = value;
656 }
657 }
658
659 if (fallback) this.mtproto = true;
660
661 return result;
662 }
663
664 getOffset() {
665 return this.offset;
666 }
667
668 fetchEnd() {
669 if (this.offset !== this.byteView.length) throw new Error('Fetch end with non-empty buffer');
670 return true;
671 }
672
673 }
674
675 return { Serialization, Deserialization };
676};
677
678exports.default = TL;
679//# sourceMappingURL=tl.js.map
\No newline at end of file