mtproto 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/data/tl-schema.json +42686 -0
  3. data/ext/aes_ige/extconf.rb +3 -9
  4. data/ext/factorization/extconf.rb +2 -0
  5. data/lib/mtproto/auth_key_generator.rb +68 -105
  6. data/lib/mtproto/binary.rb +21 -0
  7. data/lib/mtproto/client/api/check_password.rb +41 -0
  8. data/lib/mtproto/client/api/export_login_token.rb +27 -0
  9. data/lib/mtproto/client/api/get_dialogs.rb +21 -0
  10. data/lib/mtproto/client/api/get_history.rb +20 -0
  11. data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
  12. data/lib/mtproto/client/api/get_updates_state.rb +14 -0
  13. data/lib/mtproto/client/api/get_users.rb +14 -0
  14. data/lib/mtproto/client/api/import_login_token.rb +23 -0
  15. data/lib/mtproto/client/api/send_code.rb +21 -0
  16. data/lib/mtproto/client/api/sign_in.rb +27 -0
  17. data/lib/mtproto/client/api.rb +36 -0
  18. data/lib/mtproto/client/rpc/response.rb +63 -0
  19. data/lib/mtproto/client/rpc.rb +60 -127
  20. data/lib/mtproto/client.rb +143 -32
  21. data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
  22. data/lib/mtproto/crypto/dh_validator.rb +17 -19
  23. data/lib/mtproto/crypto/factorization.rb +1 -1
  24. data/lib/mtproto/crypto/rsa_key.rb +2 -2
  25. data/lib/mtproto/crypto/srp.rb +117 -0
  26. data/lib/mtproto/delegate_methods.rb +11 -0
  27. data/lib/mtproto/errors.rb +8 -0
  28. data/lib/mtproto/message/message.rb +85 -0
  29. data/lib/mtproto/session.rb +1 -1
  30. data/lib/mtproto/tl/constructor_names.rb +2271 -0
  31. data/lib/mtproto/tl/constructors.rb +99 -0
  32. data/lib/mtproto/tl/object.rb +25 -0
  33. data/lib/mtproto/tl/objects/account_password.rb +69 -0
  34. data/lib/mtproto/tl/objects/authorization.rb +70 -0
  35. data/lib/mtproto/tl/objects/check_password.rb +43 -0
  36. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +45 -0
  37. data/lib/mtproto/tl/objects/dh_gen_response.rb +46 -0
  38. data/lib/mtproto/tl/objects/dialogs.rb +453 -0
  39. data/lib/mtproto/tl/objects/export_login_token.rb +48 -0
  40. data/lib/mtproto/tl/objects/get_config.rb +13 -0
  41. data/lib/mtproto/tl/objects/get_dialogs.rb +51 -0
  42. data/lib/mtproto/tl/objects/get_difference.rb +34 -0
  43. data/lib/mtproto/tl/objects/get_history.rb +49 -0
  44. data/lib/mtproto/tl/objects/get_password.rb +13 -0
  45. data/lib/mtproto/tl/objects/get_state.rb +13 -0
  46. data/lib/mtproto/tl/objects/get_users.rb +16 -0
  47. data/lib/mtproto/{type → tl/objects}/gzip_packed.rb +6 -6
  48. data/lib/mtproto/tl/objects/help_config.rb +76 -0
  49. data/lib/mtproto/tl/objects/import_login_token.rb +37 -0
  50. data/lib/mtproto/tl/objects/init_connection.rb +57 -0
  51. data/lib/mtproto/tl/objects/invoke_with_layer.rb +20 -0
  52. data/lib/mtproto/tl/objects/login_token.rb +78 -0
  53. data/lib/mtproto/{type → tl/objects}/message.rb +3 -3
  54. data/lib/mtproto/tl/objects/messages.rb +162 -0
  55. data/lib/mtproto/{type → tl/objects}/msg_container.rb +1 -3
  56. data/lib/mtproto/{type → tl/objects}/new_session_created.rb +1 -3
  57. data/lib/mtproto/tl/objects/pq_inner_data.rb +66 -0
  58. data/lib/mtproto/tl/objects/req_dh_params.rb +63 -0
  59. data/lib/mtproto/tl/objects/req_pq_multi.rb +21 -0
  60. data/lib/mtproto/tl/objects/res_pq.rb +73 -0
  61. data/lib/mtproto/{type → tl/objects}/rpc_error.rb +1 -4
  62. data/lib/mtproto/tl/objects/send_code.rb +47 -0
  63. data/lib/mtproto/tl/objects/sent_code.rb +79 -0
  64. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
  65. data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
  66. data/lib/mtproto/tl/objects/set_client_dh_params.rb +46 -0
  67. data/lib/mtproto/tl/objects/sign_in.rb +45 -0
  68. data/lib/mtproto/tl/objects/update.rb +77 -0
  69. data/lib/mtproto/tl/objects/update_short.rb +20 -0
  70. data/lib/mtproto/tl/objects/update_short_message.rb +65 -0
  71. data/lib/mtproto/tl/objects/updates_difference.rb +152 -0
  72. data/lib/mtproto/tl/objects/updates_state.rb +35 -0
  73. data/lib/mtproto/tl/objects/users.rb +83 -0
  74. data/lib/mtproto/tl/schema.rb +102 -0
  75. data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
  76. data/lib/mtproto/transport/connection.rb +23 -0
  77. data/lib/mtproto/transport/errors.rb +11 -0
  78. data/lib/mtproto/transport/packet.rb +19 -0
  79. data/lib/mtproto/transport/tcp_connection.rb +57 -46
  80. data/lib/mtproto/updates_poller.rb +37 -33
  81. data/lib/mtproto/version.rb +1 -1
  82. data/lib/mtproto.rb +17 -27
  83. data/scripts/generate_constructors.rb +65 -0
  84. metadata +76 -61
  85. data/lib/mtproto/async/middleware/base.rb +0 -17
  86. data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
  87. data/lib/mtproto/async/request.rb +0 -18
  88. data/lib/mtproto/async/request_queue.rb +0 -63
  89. data/lib/mtproto/async_client.rb +0 -201
  90. data/lib/mtproto/rpc/get_config.rb +0 -34
  91. data/lib/mtproto/rpc/get_contacts.rb +0 -29
  92. data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
  93. data/lib/mtproto/rpc/get_updates_state.rb +0 -29
  94. data/lib/mtproto/rpc/get_users.rb +0 -29
  95. data/lib/mtproto/rpc/ping.rb +0 -33
  96. data/lib/mtproto/rpc/send_code.rb +0 -41
  97. data/lib/mtproto/rpc/send_message.rb +0 -47
  98. data/lib/mtproto/rpc/sign_in.rb +0 -48
  99. data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
  100. data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
  101. data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
  102. data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
  103. data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
  104. data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
  105. data/lib/mtproto/type/bad_msg_notification.rb +0 -46
  106. data/lib/mtproto/type/client_dh_inner_data.rb +0 -29
  107. data/lib/mtproto/type/code_settings.rb +0 -25
  108. data/lib/mtproto/type/config.rb +0 -124
  109. data/lib/mtproto/type/pq_inner_data.rb +0 -41
  110. data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
  111. data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
  112. data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
  113. data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
  114. data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
  115. data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
  116. data/lib/mtproto/type/rpc/help/config.rb +0 -35
  117. data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
  118. data/lib/mtproto/type/rpc/init_connection.rb +0 -28
  119. data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
  120. data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
  121. data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
  122. data/lib/mtproto/type/rpc/ping.rb +0 -18
  123. data/lib/mtproto/type/rpc/pong.rb +0 -46
  124. data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
  125. data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
  126. data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
  127. data/lib/mtproto/type/rpc/updates/state.rb +0 -59
  128. data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
  129. data/lib/mtproto/type/rpc/users/users.rb +0 -99
  130. data/lib/mtproto/type/sent_code.rb +0 -128
  131. data/lib/mtproto/type/serializer.rb +0 -55
  132. data/lib/mtproto/type/server_dh_inner_data.rb +0 -85
@@ -4,10 +4,8 @@ require 'zlib'
4
4
  require 'stringio'
5
5
 
6
6
  module MTProto
7
- module Type
7
+ module TL
8
8
  module GzipPacked
9
- CONSTRUCTOR = 0x3072cfa1
10
-
11
9
  module_function
12
10
 
13
11
  def unpack(data)
@@ -19,11 +17,11 @@ module MTProto
19
17
 
20
18
  if length_byte == 254
21
19
  length_bytes = data[offset, 3]
22
- length = (length_bytes + "\x00").unpack1('L<')
20
+ length = "#{length_bytes}\u0000".unpack1('L<')
23
21
  offset += 3
24
22
  puts " [GZIP] Extended length: #{length} bytes" if $DEBUG
25
23
  elsif length_byte == 255
26
- raise "Invalid TL string length: 255"
24
+ raise 'Invalid TL string length: 255'
27
25
  else
28
26
  length = length_byte
29
27
  puts " [GZIP] Short length: #{length} bytes" if $DEBUG
@@ -32,7 +30,9 @@ module MTProto
32
30
  raise "Invalid length: #{length.inspect}" unless length.is_a?(Integer) && length > 0
33
31
 
34
32
  compressed_data = data[offset, length]
35
- raise "Not enough data: expected #{length}, got #{compressed_data&.bytesize}" if compressed_data.nil? || compressed_data.bytesize < length
33
+ if compressed_data.nil? || compressed_data.bytesize < length
34
+ raise "Not enough data: expected #{length}, got #{compressed_data&.bytesize}"
35
+ end
36
36
 
37
37
  Zlib::GzipReader.new(StringIO.new(compressed_data)).read.force_encoding(Encoding::BINARY)
38
38
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class HelpConfig
6
+ attr_reader :flags, :date, :expires, :test_mode, :this_dc, :dc_options
7
+
8
+ def initialize(flags:, date:, expires:, test_mode:, this_dc:, dc_options:)
9
+ @flags = flags
10
+ @date = date
11
+ @expires = expires
12
+ @test_mode = test_mode
13
+ @this_dc = this_dc
14
+ @dc_options = dc_options
15
+ end
16
+
17
+ def self.parse(data)
18
+ constructor = data[0, 4].unpack1('L<')
19
+ unless [Constructors::CONFIG, Constructors::GZIP_PACKED].include?(constructor)
20
+ raise UnexpectedConstructorError, constructor
21
+ end
22
+
23
+ offset = 4
24
+ flags = data[offset, 4].unpack1('L<')
25
+ offset += 4
26
+ date = data[offset, 4].unpack1('L<')
27
+ offset += 4
28
+ expires = data[offset, 4].unpack1('L<')
29
+ offset += 4
30
+ test_mode = data[offset, 4].unpack1('L<') == Constructors::BOOL_TRUE
31
+ offset += 4
32
+ this_dc = data[offset, 4].unpack1('L<')
33
+ offset += 4
34
+
35
+ offset += 4 # vector constructor
36
+ dc_count = data[offset, 4].unpack1('L<')
37
+ offset += 4
38
+
39
+ dc_options = []
40
+ dc_count.times do
41
+ dc_option, offset = parse_dc_option(data, offset)
42
+ dc_options << dc_option
43
+ end
44
+
45
+ new(
46
+ flags: flags, date: date, expires: expires,
47
+ test_mode: test_mode, this_dc: this_dc, dc_options: dc_options
48
+ )
49
+ end
50
+
51
+ class << self
52
+ private
53
+
54
+ def parse_dc_option(data, offset)
55
+ offset += 4 # constructor
56
+ flags = data[offset, 4].unpack1('L<')
57
+ offset += 4
58
+ id = data[offset, 4].unpack1('L<')
59
+ offset += 4
60
+
61
+ ip_len = data.getbyte(offset)
62
+ offset += 1
63
+ ip_address = data[offset, ip_len]
64
+ offset += ip_len
65
+ padding = (4 - ((ip_len + 1) % 4)) % 4
66
+ offset += padding
67
+
68
+ port = data[offset, 4].unpack1('L<')
69
+ offset += 4
70
+
71
+ [{ id: id, ip_address: ip_address, port: port, flags: flags }, offset]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ImportLoginToken
6
+ include Binary
7
+
8
+ attr_reader :token
9
+
10
+ def initialize(token:)
11
+ @token = token
12
+ end
13
+
14
+ def body
15
+ u32_b(Constructors::AUTH_IMPORT_LOGIN_TOKEN) + serialize_tl_bytes(@token)
16
+ end
17
+
18
+ private
19
+
20
+ def serialize_tl_bytes(data)
21
+ bytes = data.bytes
22
+ length = bytes.length
23
+
24
+ if length <= 253
25
+ [length] + bytes + padding(length + 1)
26
+ else
27
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
28
+ end
29
+ end
30
+
31
+ def padding(current_length)
32
+ pad_length = (4 - (current_length % 4)) % 4
33
+ [0] * pad_length
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class InitConnection
6
+ include Binary
7
+
8
+ attr_reader :api_id, :device_model, :system_version, :app_version,
9
+ :system_lang_code, :lang_pack, :lang_code, :query
10
+
11
+ def initialize(api_id:, device_model:, system_version:, app_version:,
12
+ system_lang_code:, lang_pack:, lang_code:, query:)
13
+ @api_id = api_id
14
+ @device_model = device_model
15
+ @system_version = system_version
16
+ @app_version = app_version
17
+ @system_lang_code = system_lang_code
18
+ @lang_pack = lang_pack
19
+ @lang_code = lang_code
20
+ @query = query
21
+ end
22
+
23
+ def body
24
+ flags = 0
25
+
26
+ result = u32_b(Constructors::INIT_CONNECTION)
27
+ result += u32_b(flags)
28
+ result += u32_b(@api_id)
29
+ result += serialize_tl_string(@device_model)
30
+ result += serialize_tl_string(@system_version)
31
+ result += serialize_tl_string(@app_version)
32
+ result += serialize_tl_string(@system_lang_code)
33
+ result += serialize_tl_string(@lang_pack)
34
+ result += serialize_tl_string(@lang_code)
35
+ result + @query.body
36
+ end
37
+
38
+ private
39
+
40
+ def serialize_tl_string(str)
41
+ bytes = str.encode('UTF-8').bytes
42
+ length = bytes.length
43
+
44
+ if length <= 253
45
+ [length] + bytes + padding(length + 1)
46
+ else
47
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
48
+ end
49
+ end
50
+
51
+ def padding(current_length)
52
+ pad_length = (4 - (current_length % 4)) % 4
53
+ [0] * pad_length
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class InvokeWithLayer
6
+ include Binary
7
+
8
+ attr_reader :layer, :query
9
+
10
+ def initialize(layer:, query:)
11
+ @layer = layer
12
+ @query = query
13
+ end
14
+
15
+ def body
16
+ u32_b(Constructors::INVOKE_WITH_LAYER) + u32_b(@layer) + @query.body
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'authorization'
4
+
5
+ module MTProto
6
+ module TL
7
+ class LoginToken
8
+ attr_reader :type, :expires, :token, :dc_id, :authorization
9
+
10
+ def initialize(type:, expires: nil, token: nil, dc_id: nil, authorization: nil)
11
+ @type = type
12
+ @expires = expires
13
+ @token = token
14
+ @dc_id = dc_id
15
+ @authorization = authorization
16
+ end
17
+
18
+ def user_id
19
+ @authorization&.user_id
20
+ end
21
+
22
+ def access_hash
23
+ @authorization&.access_hash
24
+ end
25
+
26
+ def success?
27
+ @type == :success
28
+ end
29
+
30
+ def self.parse(data)
31
+ constructor = data[0, 4].unpack1('L<')
32
+
33
+ case constructor
34
+ when Constructors::AUTH_LOGIN_TOKEN
35
+ parse_token(data)
36
+ when Constructors::AUTH_LOGIN_TOKEN_MIGRATE_TO
37
+ parse_migrate_to(data)
38
+ when Constructors::AUTH_LOGIN_TOKEN_SUCCESS
39
+ new(type: :success, authorization: Authorization.parse(data[4..]))
40
+ else
41
+ raise UnexpectedConstructorError, constructor
42
+ end
43
+ end
44
+
45
+ class << self
46
+ private
47
+
48
+ def parse_token(data)
49
+ offset = 4
50
+ expires = data[offset, 4].unpack1('L<')
51
+ offset += 4
52
+ token = read_tl_bytes(data, offset)
53
+ new(type: :token, expires: expires, token: token)
54
+ end
55
+
56
+ def parse_migrate_to(data)
57
+ offset = 4
58
+ dc_id = data[offset, 4].unpack1('L<')
59
+ offset += 4
60
+ token = read_tl_bytes(data, offset)
61
+ new(type: :migrate_to, dc_id: dc_id, token: token)
62
+ end
63
+
64
+ def read_tl_bytes(data, offset)
65
+ first_byte = data.getbyte(offset)
66
+ if first_byte < 254
67
+ data[offset + 1, first_byte]
68
+ else
69
+ len = data.getbyte(offset + 1) |
70
+ (data.getbyte(offset + 2) << 8) |
71
+ (data.getbyte(offset + 3) << 16)
72
+ data[offset + 4, len]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module Type
4
+ module TL
5
5
  class Message
6
6
  attr_reader :auth_key_id, :msg_id, :body
7
7
 
@@ -22,8 +22,8 @@ module MTProto
22
22
  def self.deserialize(data)
23
23
  if data.bytesize < 20
24
24
  raise(ArgumentError,
25
- "Invalid MTProto message: expected at least 20 bytes, got #{data.bytesize} bytes (hex: #{data.unpack1('H*')})",
26
- )
25
+ 'Invalid MTProto message: expected at least 20 bytes, ' \
26
+ "got #{data.bytesize} bytes (hex: #{data.unpack1('H*')})")
27
27
  end
28
28
 
29
29
  auth_key_id = data[0, 8].unpack1('Q<')
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../schema'
4
+
5
+ module MTProto
6
+ module TL
7
+ class Messages
8
+ Message = Struct.new(:id, :from_id, :date, :text, keyword_init: true)
9
+
10
+ MESSAGES_MESSAGES = 0x8c718e87
11
+ MESSAGES_SLICE = 0x762b263d
12
+ MESSAGES_CHANNEL = 0xc776ba4e
13
+
14
+ MESSAGE = Constructors::MESSAGE
15
+ MESSAGE_SERVICE = 0x7a800e0a
16
+ MESSAGE_EMPTY = 0x90a6ca84
17
+
18
+ VALID_CONSTRUCTORS = [MESSAGES_MESSAGES, MESSAGES_SLICE, MESSAGES_CHANNEL].freeze
19
+
20
+ attr_reader :messages, :count
21
+
22
+ def initialize(messages:, count: nil)
23
+ @messages = messages
24
+ @count = count
25
+ end
26
+
27
+ def slice?
28
+ !@count.nil?
29
+ end
30
+
31
+ def self.schema
32
+ @schema ||= Schema.new(
33
+ File.expand_path('../../../../data/tl-schema.json', __dir__)
34
+ )
35
+ end
36
+
37
+ def self.parse(data)
38
+ constructor = data[0, 4].unpack1('L<')
39
+ raise UnexpectedConstructorError, constructor unless VALID_CONSTRUCTORS.include?(constructor)
40
+
41
+ offset = 4
42
+ count = nil
43
+
44
+ case constructor
45
+ when MESSAGES_MESSAGES
46
+ # messages:Vector<Message> chats:Vector<Chat> users:Vector<User>
47
+ when MESSAGES_SLICE
48
+ # flags:# count:int next_rate:flags.0?int offset_id_offset:flags.2?int
49
+ # search_flood:flags.3?SearchPostsFlood messages:Vector<Message> ...
50
+ flags = data[offset, 4].unpack1('L<')
51
+ offset += 4
52
+ count = data[offset, 4].unpack1('L<')
53
+ offset += 4
54
+ offset += 4 if flags.anybits?(1 << 0) # next_rate
55
+ offset += 4 if flags.anybits?(1 << 2) # offset_id_offset
56
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # search_flood
57
+ when MESSAGES_CHANNEL
58
+ # flags:# pts:int count:int offset_id_offset:flags.2?int
59
+ # messages:Vector<Message> topics:Vector<ForumTopic> ...
60
+ flags = data[offset, 4].unpack1('L<')
61
+ offset += 4
62
+ offset += 4 # pts
63
+ count = data[offset, 4].unpack1('L<')
64
+ offset += 4
65
+ offset += 4 if flags.anybits?(1 << 2) # offset_id_offset
66
+ end
67
+
68
+ messages, offset = parse_messages_vector(data, offset)
69
+
70
+ # skip topics vector for channelMessages
71
+ schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if constructor == MESSAGES_CHANNEL
72
+
73
+ # skip chats and users (we don't need them here)
74
+
75
+ new(messages: messages, count: count)
76
+ end
77
+
78
+ class << self
79
+ private
80
+
81
+ def parse_messages_vector(data, offset)
82
+ offset += 4 # vector constructor
83
+ count = data[offset, 4].unpack1('L<')
84
+ offset += 4
85
+
86
+ messages = []
87
+ count.times do
88
+ constructor = data[offset, 4].unpack1('L<')
89
+
90
+ if constructor == MESSAGE
91
+ msg, = parse_message(data, offset)
92
+ messages << msg if msg
93
+ end
94
+
95
+ offset = schema.skip(data, offset)
96
+ end
97
+ [messages, offset]
98
+ end
99
+
100
+ # message#9815cec8 flags:# flags2:# id:int
101
+ # from_id:flags.8?Peer ... date:int message:string ...
102
+ def parse_message(data, offset)
103
+ offset += 4 # constructor
104
+ flags = data[offset, 4].unpack1('L<')
105
+ offset += 4
106
+ flags2 = data[offset, 4].unpack1('L<')
107
+ offset += 4
108
+ id = data[offset, 4].unpack1('l<')
109
+ offset += 4
110
+
111
+ from_id = nil
112
+ _, from_id, offset = parse_peer(data, offset) if flags.anybits?(1 << 8)
113
+ offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
114
+ _, _, offset = parse_peer(data, offset) # peer_id (skip)
115
+ _, _, offset = parse_peer(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
116
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 2) # fwd_from
117
+ offset += 8 if flags.anybits?(1 << 11) # via_bot_id
118
+ offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
119
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
120
+
121
+ date = data[offset, 4].unpack1('L<')
122
+ offset += 4
123
+
124
+ text, offset = read_tl_string(data, offset)
125
+
126
+ msg = Message.new(id: id, from_id: from_id, date: date, text: text)
127
+ [msg, offset]
128
+ end
129
+
130
+ def parse_peer(data, offset)
131
+ constructor = data[offset, 4].unpack1('L<')
132
+ offset += 4
133
+ case constructor
134
+ when Constructors::PEER_USER
135
+ [:user, data[offset, 8].unpack1('Q<'), offset + 8]
136
+ when Constructors::PEER_CHAT
137
+ [:chat, data[offset, 8].unpack1('Q<'), offset + 8]
138
+ when Constructors::PEER_CHANNEL
139
+ [:channel, data[offset, 8].unpack1('Q<'), offset + 8]
140
+ else
141
+ raise "Unknown peer constructor: 0x#{constructor.to_s(16)}"
142
+ end
143
+ end
144
+
145
+ def read_tl_string(data, offset)
146
+ first_byte = data.getbyte(offset)
147
+ if first_byte == 254
148
+ len = data.getbyte(offset + 1) |
149
+ (data.getbyte(offset + 2) << 8) |
150
+ (data.getbyte(offset + 3) << 16)
151
+ total = 4 + len
152
+ else
153
+ len = first_byte
154
+ total = 1 + len
155
+ end
156
+ padding = (4 - (total % 4)) % 4
157
+ [data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module Type
4
+ module TL
5
5
  class MsgContainer
6
- CONSTRUCTOR = 0x73f1f8dc
7
-
8
6
  attr_reader :messages
9
7
 
10
8
  def self.deserialize(data)
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module Type
4
+ module TL
5
5
  class NewSessionCreated
6
- CONSTRUCTOR = 0x9ec20908
7
-
8
6
  attr_reader :first_msg_id, :unique_id, :server_salt
9
7
 
10
8
  def self.deserialize(data)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class PQInnerData
6
+ include Binary
7
+
8
+ attr_reader :pq, :p, :q, :nonce, :server_nonce, :new_nonce, :dc, :expires_in
9
+
10
+ def initialize(pq:, p:, q:, nonce:, server_nonce:, new_nonce:, dc:, expires_in: nil)
11
+ @pq = pq
12
+ @p = p
13
+ @q = q
14
+ @nonce = nonce
15
+ @server_nonce = server_nonce
16
+ @new_nonce = new_nonce
17
+ @dc = dc
18
+ @expires_in = expires_in
19
+ end
20
+
21
+ def body
22
+ constructor = @expires_in ? Constructors::PQ_INNER_DATA_TEMP_DC : Constructors::PQ_INNER_DATA_DC
23
+
24
+ result = u32_b(constructor)
25
+ result += serialize_tl_bytes(integer_to_bytes(@pq))
26
+ result += serialize_tl_bytes(integer_to_bytes(@p))
27
+ result += serialize_tl_bytes(integer_to_bytes(@q))
28
+ result += @nonce.bytes
29
+ result += @server_nonce.bytes
30
+ result += @new_nonce.bytes
31
+ result += u32_b(@expires_in) if @expires_in
32
+ result += u32_b(@dc)
33
+
34
+ result
35
+ end
36
+
37
+ private
38
+
39
+ def serialize_tl_bytes(bytes)
40
+ length = bytes.length
41
+
42
+ if length <= 253
43
+ [length] + bytes + padding(length + 1)
44
+ else
45
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
46
+ end
47
+ end
48
+
49
+ def padding(current_length)
50
+ pad_length = (4 - (current_length % 4)) % 4
51
+ [0] * pad_length
52
+ end
53
+
54
+ def integer_to_bytes(int)
55
+ return [0] if int.zero?
56
+
57
+ bytes = []
58
+ while int > 0
59
+ bytes.unshift(int & 0xff)
60
+ int >>= 8
61
+ end
62
+ bytes
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ReqDHParams
6
+ include Binary
7
+
8
+ attr_reader :nonce, :server_nonce, :p, :q, :public_key_fingerprint, :encrypted_data
9
+
10
+ def initialize(nonce:, server_nonce:, p:, q:, public_key_fingerprint:, encrypted_data:)
11
+ raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
12
+ raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
13
+
14
+ @nonce = nonce
15
+ @server_nonce = server_nonce
16
+ @p = p
17
+ @q = q
18
+ @public_key_fingerprint = public_key_fingerprint
19
+ @encrypted_data = encrypted_data
20
+ end
21
+
22
+ def body
23
+ result = u32_b(Constructors::REQ_DH_PARAMS)
24
+ result += @nonce.bytes
25
+ result += @server_nonce.bytes
26
+ result += serialize_tl_bytes(integer_to_bytes(@p))
27
+ result += serialize_tl_bytes(integer_to_bytes(@q))
28
+ result += u64_b(@public_key_fingerprint)
29
+ result += serialize_tl_bytes(@encrypted_data.bytes)
30
+
31
+ result
32
+ end
33
+
34
+ private
35
+
36
+ def serialize_tl_bytes(bytes)
37
+ length = bytes.length
38
+
39
+ if length <= 253
40
+ [length] + bytes + padding(length + 1)
41
+ else
42
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
43
+ end
44
+ end
45
+
46
+ def padding(current_length)
47
+ pad_length = (4 - (current_length % 4)) % 4
48
+ [0] * pad_length
49
+ end
50
+
51
+ def integer_to_bytes(int)
52
+ return [0] if int.zero?
53
+
54
+ bytes = []
55
+ while int > 0
56
+ bytes.unshift(int & 0xff)
57
+ int >>= 8
58
+ end
59
+ bytes
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ReqPqMulti
6
+ include Binary
7
+
8
+ attr_reader :nonce
9
+
10
+ def initialize(nonce)
11
+ raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
12
+
13
+ @nonce = nonce
14
+ end
15
+
16
+ def body
17
+ u32_b(Constructors::REQ_PQ_MULTI) + @nonce.bytes
18
+ end
19
+ end
20
+ end
21
+ end