parser.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. const fs = require('fs');
  2. const { TLArg } = require('./tlarg');
  3. const { TLObject } = require('./tlobject');
  4. const { Usability } = require('../methods');
  5. const CORE_TYPES = new Set([
  6. 0xbc799737, // boolFalse#bc799737 = Bool;
  7. 0x997275b5, // boolTrue#997275b5 = Bool;
  8. 0x3fedd339, // true#3fedd339 = True;
  9. 0xc4b9f9bb, // error#c4b9f9bb code:int text:string = Error;
  10. 0x56730bcc, // null#56730bcc = Null;
  11. ]);
  12. // Telegram Desktop (C++) doesn't care about string/bytes, and the .tl files
  13. // don't either. However in Python we *do*, and we want to deal with bytes
  14. // for the authorization key process, not UTF-8 strings (they won't be).
  15. //
  16. // Every type with an ID that's in here should get their attribute types
  17. // with string being replaced with bytes.
  18. const AUTH_KEY_TYPES = new Set([
  19. 0x05162463, // resPQ,
  20. 0x83c95aec, // p_q_inner_data
  21. 0xa9f55f95, // p_q_inner_data_dc
  22. 0x3c6a84d4, // p_q_inner_data_temp
  23. 0x56fddf88, // p_q_inner_data_temp_dc
  24. 0xd0e8075c, // server_DH_params_ok
  25. 0xb5890dba, // server_DH_inner_data
  26. 0x6643b654, // client_DH_inner_data
  27. 0xd712e4be, // req_DH_params
  28. 0xf5045f1f, // set_client_DH_params
  29. 0x3072cfa1, // gzip_packed
  30. ]);
  31. const findall = (regex, str, matches) => {
  32. if (!matches) {
  33. matches = [];
  34. }
  35. if (!regex.flags.includes(`g`)) {
  36. regex = new RegExp(regex.source, `g`);
  37. }
  38. const res = regex.exec(str);
  39. if (res) {
  40. matches.push(res.slice(1));
  41. findall(regex, str, matches);
  42. }
  43. return matches;
  44. };
  45. const fromLine = (line, isFunction, methodInfo, layer) => {
  46. const match = line.match(
  47. /([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/
  48. );
  49. if (!match) {
  50. // Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
  51. throw new Error(`Cannot parse TLObject ${line}`);
  52. }
  53. const argsMatch = findall(/({)?(\w+):([\w\d<>#.?!]+)}?/, line);
  54. const [, name] = match;
  55. methodInfo = methodInfo[name];
  56. let usability, friendly;
  57. if (methodInfo) {
  58. usability = methodInfo.usability;
  59. friendly = methodInfo.friendly;
  60. } else {
  61. usability = Usability.UNKNOWN;
  62. friendly = null;
  63. }
  64. return new TLObject(
  65. name,
  66. match[2],
  67. argsMatch.map(
  68. ([brace, name, argType]) =>
  69. new TLArg(name, argType, brace !== undefined)
  70. ),
  71. match[3],
  72. isFunction,
  73. usability,
  74. friendly,
  75. layer
  76. );
  77. };
  78. /**
  79. * This method yields TLObjects from a given .tl file.
  80. *
  81. * Note that the file is parsed completely before the function yields
  82. * because references to other objects may appear later in the file.
  83. */
  84. const parseTl = function*(filePath, layer, methods, ignoreIds = CORE_TYPES) {
  85. const methodInfo = (methods || []).reduce(
  86. (o, m) => ({ ...o, [m.name]: m }),
  87. {}
  88. );
  89. const objAll = [];
  90. const objByName = {};
  91. const objByType = {};
  92. const file = fs.readFileSync(filePath, { encoding: 'utf-8' });
  93. let isFunction = false;
  94. for (let line of file.split('\n')) {
  95. const commentIndex = line.indexOf('//');
  96. if (commentIndex !== -1) {
  97. line = line.slice(0, commentIndex);
  98. }
  99. line = line.trim();
  100. if (!line) {
  101. continue;
  102. }
  103. const match = line.match(/---(\w+)---/);
  104. if (match) {
  105. const [, followingTypes] = match;
  106. isFunction = followingTypes === 'functions';
  107. continue;
  108. }
  109. try {
  110. const result = fromLine(line, isFunction, methodInfo, layer);
  111. if (ignoreIds.has(result.id)) {
  112. continue;
  113. }
  114. objAll.push(result);
  115. if (!result.isFunction) {
  116. if (!objByType[result.result]) {
  117. objByType[result.result] = [];
  118. }
  119. objByName[result.fullname] = result;
  120. objByType[result.result].push(result);
  121. }
  122. } catch (e) {
  123. if (!e.toString().includes('vector#1cb5c415')) {
  124. throw e;
  125. }
  126. }
  127. }
  128. // Once all objects have been parsed, replace the
  129. // string type from the arguments with references
  130. for (const obj of objAll) {
  131. if (AUTH_KEY_TYPES.has(obj.id)) {
  132. for (const arg of obj.args) {
  133. if (arg.type === 'string') {
  134. arg.type = 'bytes';
  135. }
  136. }
  137. }
  138. for (const arg of obj.args) {
  139. arg.cls =
  140. objByType[arg.type] ||
  141. (arg.type in objByName ? [objByName[arg.type]] : []);
  142. }
  143. }
  144. for (const obj of objAll) {
  145. yield obj;
  146. }
  147. };
  148. /**
  149. * Finds the layer used on the specified scheme.tl file.
  150. */
  151. const findLayer = filePath => {
  152. const layerRegex = /^\/\/\s*LAYER\s*(\d+)$/;
  153. const file = fs.readFileSync(filePath, { encoding: 'utf-8' });
  154. for (const line of file.split('\n')) {
  155. const match = line.match(layerRegex);
  156. if (match) {
  157. return Number(match[1]);
  158. }
  159. }
  160. };
  161. module.exports = {
  162. parseTl,
  163. findLayer,
  164. };