parser.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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(/([\w.]+)(?:#([0-9a-fA-F]+))?(?:\s{?\w+:[\w\d<>#.?!]+}?)*\s=\s([\w\d<>#.?]+);$/)
  47. if (!match) {
  48. // Probably "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
  49. throw new Error(`Cannot parse TLObject ${line}`)
  50. }
  51. const argsMatch = findall(/({)?(\w+):([\w\d<>#.?!]+)}?/, line)
  52. const [, name] = match
  53. methodInfo = methodInfo[name]
  54. let usability
  55. let friendly
  56. if (methodInfo) {
  57. usability = methodInfo.usability
  58. friendly = methodInfo.friendly
  59. } else {
  60. usability = Usability.UNKNOWN
  61. friendly = null
  62. }
  63. return new TLObject(
  64. name,
  65. match[2],
  66. argsMatch.map(([brace, name, argType]) => new TLArg(name, argType, brace !== undefined)),
  67. match[3],
  68. isFunction,
  69. usability,
  70. friendly,
  71. layer,
  72. )
  73. }
  74. /**
  75. * This method yields TLObjects from a given .tl file.
  76. *
  77. * Note that the file is parsed completely before the function yields
  78. * because references to other objects may appear later in the file.
  79. */
  80. const parseTl = function* (filePath, layer, methods, ignoreIds = CORE_TYPES) {
  81. const methodInfo = (methods || []).reduce((o, m) => ({ ...o, [m.name]: m }), {})
  82. const objAll = []
  83. const objByName = {}
  84. const objByType = {}
  85. const file = fs.readFileSync(filePath, { encoding: 'utf-8' })
  86. let isFunction = false
  87. for (let line of file.split('\n')) {
  88. const commentIndex = line.indexOf('//')
  89. if (commentIndex !== -1) {
  90. line = line.slice(0, commentIndex)
  91. }
  92. line = line.trim()
  93. if (!line) {
  94. continue
  95. }
  96. const match = line.match(/---(\w+)---/)
  97. if (match) {
  98. const [, followingTypes] = match
  99. isFunction = followingTypes === 'functions'
  100. continue
  101. }
  102. try {
  103. const result = fromLine(line, isFunction, methodInfo, layer)
  104. if (ignoreIds.has(result.id)) {
  105. continue
  106. }
  107. objAll.push(result)
  108. if (!result.isFunction) {
  109. if (!objByType[result.result]) {
  110. objByType[result.result] = []
  111. }
  112. objByName[result.fullname] = result
  113. objByType[result.result].push(result)
  114. }
  115. } catch (e) {
  116. if (!e.toString().includes('vector#1cb5c415')) {
  117. throw e
  118. }
  119. }
  120. }
  121. // Once all objects have been parsed, replace the
  122. // string type from the arguments with references
  123. for (const obj of objAll) {
  124. if (AUTH_KEY_TYPES.has(obj.id)) {
  125. for (const arg of obj.args) {
  126. if (arg.type === 'string') {
  127. arg.type = 'bytes'
  128. }
  129. }
  130. }
  131. for (const arg of obj.args) {
  132. arg.cls = objByType[arg.type] || (arg.type in objByName ? [objByName[arg.type]] : [])
  133. }
  134. }
  135. for (const obj of objAll) {
  136. yield obj
  137. }
  138. }
  139. /**
  140. * Finds the layer used on the specified scheme.tl file.
  141. */
  142. const findLayer = (filePath) => {
  143. const layerRegex = /^\/\/\s*LAYER\s*(\d+)/
  144. const file = fs.readFileSync(filePath, { encoding: 'utf-8' })
  145. for (const line of file.split('\n')) {
  146. const match = line.match(layerRegex)
  147. if (match) {
  148. return Number(match[1])
  149. }
  150. }
  151. }
  152. module.exports = {
  153. parseTl,
  154. findLayer,
  155. }