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
@@ -1,164 +1,97 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../message_id'
4
+ require_relative 'rpc/response'
4
5
 
5
6
  module MTProto
6
7
  class Client
7
8
  class RPC
8
- CONSTRUCTOR_RPC_RESULT = 0xf35c6d01
9
- CONSTRUCTOR_MSGS_ACK = 0x62d6b459
10
- CONSTRUCTOR_BAD_SERVER_SALT = 0xedab447b
9
+ attr_reader :pending_requests
11
10
 
12
11
  def initialize(client)
13
12
  @client = client
14
- @connection_initialized = false
13
+ @pending_requests = {}
15
14
  end
16
15
 
17
- def call(body, content_related: true)
18
- raise 'Auth key not generated' unless @client.auth_key
16
+ def call(request, response_class)
17
+ raise 'Mainloop not running' unless @client.mainloop_running?
18
+ raise 'Auth key not generated' unless @client.auth_key?
19
19
  raise 'Session not initialized' unless @client.session
20
20
 
21
- msg_id = MessageId.generate(time_offset: @client.time_offset)
22
- seq_no = @client.session.next_seq_no(content_related: content_related)
21
+ body = serialize_request(request)
22
+ msg_id = send_encrypted(body)
23
+ response = Response.new(msg_id, response_class, body)
24
+ @pending_requests[msg_id] = response
23
25
 
24
- encrypted_msg = EncryptedMessage.encrypt(
25
- auth_key: @client.auth_key,
26
- server_salt: @client.server_salt,
27
- session_id: @client.session.session_id,
28
- msg_id: msg_id,
29
- seq_no: seq_no,
30
- body: body
31
- )
32
-
33
- @client.connection.send(encrypted_msg.serialize)
34
-
35
- msg_id
26
+ response
36
27
  end
37
28
 
38
- def call_sync(body, content_related: true)
39
- raise 'Auth key not generated' unless @client.auth_key
40
- raise 'Session not initialized' unless @client.session
41
-
42
- msg_id = MessageId.generate(time_offset: @client.time_offset)
43
- seq_no = @client.session.next_seq_no(content_related: content_related)
44
-
45
- encrypted_msg = EncryptedMessage.encrypt(
46
- auth_key: @client.auth_key,
47
- server_salt: @client.server_salt,
48
- session_id: @client.session.session_id,
49
- msg_id: msg_id,
50
- seq_no: seq_no,
51
- body: body
52
- )
53
-
54
- @client.connection.send(encrypted_msg.serialize)
29
+ def handle_bad_server_salt(response_body, client)
30
+ bad_msg_id = response_body[4, 8].unpack1('Q<')
31
+ new_server_salt = response_body[20, 8].unpack1('Q<')
55
32
 
56
- response_data = @client.connection.recv(timeout: @client.timeout)
33
+ client.update_server_salt(new_server_salt)
57
34
 
58
- decrypted = EncryptedMessage.decrypt(
59
- auth_key: @client.auth_key,
60
- encrypted_message_data: response_data,
61
- sender: :server
62
- )
35
+ response = @pending_requests.delete(bad_msg_id)
36
+ return unless response
63
37
 
64
- response_body = decrypted[:body]
65
-
66
- constructor = response_body[0, 4].unpack1('L<')
38
+ new_msg_id = send_encrypted(response.body_bytes)
39
+ response.instance_variable_set(:@msg_id, new_msg_id)
40
+ @pending_requests[new_msg_id] = response
41
+ end
67
42
 
68
- if constructor == CONSTRUCTOR_BAD_SERVER_SALT
69
- offset = 4
70
- offset += 8
71
- offset += 4
72
- offset += 4
73
- new_server_salt = response_body[offset, 8].unpack1('Q<')
43
+ def handle_rpc_result(response_body)
44
+ req_msg_id = response_body[4, 8].unpack1('Q<')
45
+ result = response_body[12..]
74
46
 
75
- update_server_salt(new_server_salt)
76
- return call_sync(body, content_related: content_related)
47
+ result_constructor = result[0, 4].unpack1('L<')
48
+ if result_constructor == TL::Constructors::GZIP_PACKED
49
+ result = TL::GzipPacked.unpack(result)
50
+ result_constructor = result[0, 4].unpack1('L<')
77
51
  end
78
52
 
79
- if constructor == Type::NewSessionCreated::CONSTRUCTOR
80
- session_info = Type::NewSessionCreated.deserialize(response_body)
81
- update_server_salt(session_info.server_salt)
82
-
83
- response_data = @client.connection.recv(timeout: @client.timeout)
84
- decrypted = EncryptedMessage.decrypt(
85
- auth_key: @client.auth_key,
86
- encrypted_message_data: response_data,
87
- sender: :server
88
- )
89
- response_body = decrypted[:body]
90
- constructor = response_body[0, 4].unpack1('L<')
91
- end
53
+ response = @pending_requests.delete(req_msg_id)
54
+ return unless response
92
55
 
93
- if constructor == Type::MsgContainer::CONSTRUCTOR
94
- container = Type::MsgContainer.deserialize(response_body)
95
-
96
- rpc_result = container.messages.find do |msg|
97
- msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
98
- end
99
-
100
- return extract_rpc_result(rpc_result[:body][12..]) if rpc_result
101
-
102
- new_session = container.messages.find do |msg|
103
- msg[:body][0, 4].unpack1('L<') == Type::NewSessionCreated::CONSTRUCTOR
104
- end
105
- if new_session
106
- session_info = Type::NewSessionCreated.deserialize(new_session[:body])
107
- update_server_salt(session_info.server_salt)
108
-
109
- other_messages = container.messages.reject do |msg|
110
- constructor = msg[:body][0, 4].unpack1('L<')
111
- (
112
- constructor == Type::NewSessionCreated::CONSTRUCTOR ||
113
- constructor == CONSTRUCTOR_MSGS_ACK
114
- )
115
- end
116
-
117
- return other_messages.first[:body] unless other_messages.empty?
118
-
119
- response_data = @client.connection.recv(timeout: @client.timeout)
120
- decrypted = EncryptedMessage.decrypt(
121
- auth_key: @client.auth_key,
122
- encrypted_message_data: response_data,
123
- sender: :server
124
- )
125
- response_body = decrypted[:body]
126
- constructor = response_body[0, 4].unpack1('L<')
127
-
128
- return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
129
-
130
- if constructor == Type::MsgContainer::CONSTRUCTOR
131
- container = Type::MsgContainer.deserialize(response_body)
132
- rpc_result = container.messages.find do |msg|
133
- msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
134
- end
135
- return extract_rpc_result(rpc_result[:body][12..]) if rpc_result
136
- end
137
-
138
- return response_body
139
- end
140
-
141
- return container.messages.first[:body]
56
+ if result_constructor == TL::Constructors::RPC_ERROR
57
+ error = TL::RpcError.deserialize(result)
58
+ response.signal_error(RpcError.new(error.error_code, error.error_message))
59
+ else
60
+ response.signal(result)
142
61
  end
62
+ end
143
63
 
144
- return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
145
-
146
- response_body
64
+ def signal_all_error(error)
65
+ @pending_requests.each_value { |response| response.signal_error(error) }
66
+ @pending_requests.clear
147
67
  end
148
68
 
149
69
  private
150
70
 
151
- def extract_rpc_result(result)
152
- result_constructor = result[0, 4].unpack1('L<')
153
- if result_constructor == Type::RpcError::CONSTRUCTOR
154
- error = Type::RpcError.deserialize(result)
155
- raise MTProto::RpcError.new(error.error_code, error.error_message)
71
+ def serialize_request(request)
72
+ if request.respond_to?(:body)
73
+ request.body.pack('C*')
74
+ else
75
+ request
156
76
  end
157
- result
158
77
  end
159
78
 
160
- def update_server_salt(server_salt)
161
- @client.instance_variable_set(:@server_salt, server_salt)
79
+ def send_encrypted(body, content_related: true)
80
+ msg_id = MessageId.generate(time_offset: @client.time_offset)
81
+ seq_no = @client.session.next_seq_no(content_related: content_related)
82
+
83
+ encrypted_msg = EncryptedMessage.encrypt(
84
+ auth_key: @client.auth_key,
85
+ server_salt: @client.server_salt,
86
+ session_id: @client.session.session_id,
87
+ msg_id: msg_id,
88
+ seq_no: seq_no,
89
+ body: body
90
+ )
91
+
92
+ @client.connection.send(Transport::Packet.new(encrypted_msg.serialize.bytes))
93
+
94
+ msg_id
162
95
  end
163
96
  end
164
97
  end
@@ -3,27 +3,59 @@
3
3
  require 'securerandom'
4
4
  require 'digest'
5
5
  require 'base64'
6
+ require 'async'
7
+ require 'async/condition'
6
8
  require_relative 'transport/tcp_connection'
9
+ require_relative 'transport/connection'
7
10
  require_relative 'transport/abridged_packet_codec'
8
- require_relative 'type/message'
11
+ require_relative 'tl/objects/message'
9
12
  require_relative 'client/rpc'
10
- require_relative 'type/rpc/help/get_config'
11
- require_relative 'type/rpc/help/config'
12
- require_relative 'type/rpc/invoke_with_layer'
13
- require_relative 'type/rpc/init_connection'
13
+ require_relative 'client/api'
14
+ require_relative 'tl/objects/invoke_with_layer'
15
+ require_relative 'tl/objects/init_connection'
16
+ require_relative 'tl/objects/get_config'
17
+ require_relative 'tl/objects/help_config'
14
18
 
15
19
  module MTProto
16
20
  class Client
21
+ extend DelegateMethods
17
22
 
18
- attr_reader :connection, :server_key, :auth_key, :server_salt, :time_offset, :session, :timeout, :user_id, :access_hash, :dc_number
19
- attr_accessor :api_id, :api_hash, :device_model, :system_version, :app_version, :system_lang_code, :lang_pack, :lang_code
23
+ API_LAYER = 214
20
24
 
21
- def initialize(api_id: nil, api_hash: nil, host:, port: 443, public_key: nil, dc_number: nil, test_mode: false, timeout: 10)
22
- raise ArgumentError, "host is required" if host.nil? || host.empty?
23
- raise ArgumentError, "dc_number must be positive. Use test_mode: true for test DCs" if dc_number && dc_number < 0
25
+ attr_reader :connection, :server_key, :auth_key, :server_salt, :time_offset, :session,
26
+ :timeout, :user_id, :access_hash, :dc_number
27
+ attr_accessor :api_id, :api_hash, :device_model, :system_version, :app_version,
28
+ :system_lang_code, :lang_pack, :lang_code
24
29
 
25
- codec = Transport::AbridgedPacketCodec.new
26
- @connection = Transport::TCPConnection.new(host, port, codec)
30
+ delegate :connect!, :connected?, to: :connection
31
+
32
+ def auth_key?
33
+ !@auth_key.nil?
34
+ end
35
+
36
+ def mainloop_running?
37
+ @running
38
+ end
39
+
40
+ def initialize(
41
+ host:,
42
+ api_id:,
43
+ api_hash:,
44
+ port: 443,
45
+ public_key: nil,
46
+ dc_number: nil,
47
+ test_mode: false,
48
+ timeout: 10
49
+ )
50
+ raise ArgumentError, 'host is required' if host.nil? || host.empty?
51
+
52
+ raise ArgumentError, 'dc_number must be positive. Use test_mode: true for test DCs' if dc_number && dc_number < 0
53
+
54
+ @api_id = api_id
55
+ @api_hash = api_hash
56
+
57
+ transport = Transport::TCPConnection.new(host, port)
58
+ @connection = Transport::Connection.new(transport)
27
59
  @public_key = public_key
28
60
  @dc_number = dc_number
29
61
  @test_mode = test_mode
@@ -37,33 +69,76 @@ module MTProto
37
69
  @user_id = nil
38
70
  @access_hash = nil
39
71
 
40
- # Client configuration defaults
41
- @api_id = api_id || 0
42
- @api_hash = api_hash || ''
43
72
  @device_model = 'Ruby MTProto'
44
73
  @system_version = RUBY_DESCRIPTION
45
74
  @app_version = '0.1.0'
46
75
  @system_lang_code = 'en'
47
76
  @lang_pack = ''
48
77
  @lang_code = 'en'
78
+
79
+ @receiver_task = nil
80
+ @running = false
81
+ @on_update_callbacks = []
49
82
  end
50
83
 
51
- def connect!
52
- @connection.connect!
84
+ def on_update(&block)
85
+ @on_update_callbacks << block
53
86
  end
54
87
 
55
- def connected?
56
- @connection&.connected?
88
+ def run_mainloop
89
+ raise 'Auth key not set' unless auth_key?
90
+ raise 'Mainloop already running' if @running
91
+ raise ArgumentError, 'Block is required' unless block_given?
92
+
93
+ begin
94
+ Async do
95
+ @running = true
96
+ @receiver_task = Async do
97
+ @connection.receive do |packet, error|
98
+ if error
99
+ warn "[MTProto] Packet read error: #{error.message}"
100
+ next
101
+ end
102
+
103
+ decrypted = EncryptedMessage.decrypt(
104
+ auth_key: @auth_key,
105
+ encrypted_message_data: packet.data.pack('C*'),
106
+ sender: :server
107
+ )
108
+
109
+ process_message(decrypted[:body])
110
+ end
111
+ end
112
+
113
+ yield self
114
+ ensure
115
+ disconnect!
116
+ end
117
+ rescue Interrupt
118
+ # Ctrl+C
119
+ end
57
120
  end
58
121
 
59
122
  def disconnect!
60
- @connection.close if @connection
123
+ @running = false
124
+ @receiver_task&.stop
125
+ @receiver_task = nil
126
+
127
+ rpc.signal_all_error(Transport::ConnectionError.new('Client shutting down'))
128
+
129
+ @connection.disconnect! if @connection&.connected?
61
130
  end
62
131
 
63
132
  def exchange_keys!
64
- raise ArgumentError, "public_key is required for auth key generation" if @public_key.nil? || @public_key.empty?
65
-
66
- generator = AuthKeyGenerator.new(@connection, @public_key, @dc_number, test_mode: @test_mode, timeout: @timeout)
133
+ raise ArgumentError, 'public_key is required for auth key generation' if @public_key.nil? || @public_key.empty?
134
+
135
+ generator = AuthKeyGenerator.new(
136
+ @connection,
137
+ @public_key,
138
+ @dc_number,
139
+ test_mode: @test_mode,
140
+ timeout: @timeout
141
+ )
67
142
  result = generator.generate
68
143
 
69
144
  @auth_key = generator.auth_key
@@ -78,16 +153,16 @@ module MTProto
78
153
  @rpc ||= RPC.new(self)
79
154
  end
80
155
 
81
- def call(body, content_related: true)
82
- rpc.call(body, content_related: content_related)
156
+ def api
157
+ @api ||= API.new(self)
83
158
  end
84
159
 
85
160
  def init_connection!
86
161
  return if @connection_initialized
87
162
 
88
- query = Type::RPC::InvokeWithLayer.build(
89
- layer: 214,
90
- query: Type::RPC::InitConnection.build(
163
+ query = TL::InvokeWithLayer.new(
164
+ layer: API_LAYER,
165
+ query: TL::InitConnection.new(
91
166
  api_id: api_id,
92
167
  device_model: device_model,
93
168
  system_version: system_version,
@@ -95,16 +170,16 @@ module MTProto
95
170
  system_lang_code: system_lang_code,
96
171
  lang_pack: lang_pack,
97
172
  lang_code: lang_code,
98
- query: Type::RPC::Help::GetConfig.build
173
+ query: TL::GetConfig.new
99
174
  )
100
175
  )
101
176
 
102
- response = rpc.call_sync(query)
103
- config = Type::RPC::Help::Config.parse(response)
177
+ response = rpc.call(query, TL::HelpConfig)
178
+ response.wait!(timeout)
104
179
 
105
180
  @connection_initialized = true
106
181
 
107
- config
182
+ response.body
108
183
  end
109
184
 
110
185
  def save_auth_data
@@ -140,5 +215,41 @@ module MTProto
140
215
  @user_id = user_id
141
216
  @access_hash = access_hash
142
217
  end
218
+
219
+ def update_server_salt(new_salt)
220
+ @server_salt = new_salt
221
+ end
222
+
223
+ private
224
+
225
+ def process_message(response_body)
226
+ constructor = response_body[0, 4].unpack1('L<')
227
+
228
+ case constructor
229
+ when TL::Constructors::BAD_SERVER_SALT
230
+ rpc.handle_bad_server_salt(response_body, self)
231
+ when TL::Constructors::NEW_SESSION_CREATED
232
+ handle_new_session(response_body)
233
+ when TL::Constructors::MSG_CONTAINER
234
+ handle_container(response_body)
235
+ when TL::Constructors::RPC_RESULT
236
+ rpc.handle_rpc_result(response_body)
237
+ else
238
+ @on_update_callbacks.each { |cb| cb.call(constructor, response_body) }
239
+ end
240
+ end
241
+
242
+ def handle_new_session(response_body)
243
+ session_info = TL::NewSessionCreated.deserialize(response_body)
244
+ @server_salt = session_info.server_salt
245
+ end
246
+
247
+ def handle_container(response_body)
248
+ container = TL::MsgContainer.deserialize(response_body)
249
+
250
+ container.messages.each do |msg|
251
+ process_message(msg[:body])
252
+ end
253
+ end
143
254
  end
144
255
  end
@@ -36,8 +36,7 @@ module MTProto
36
36
  random_bytes = SecureRandom.random_bytes(256)
37
37
  random_bytes[0] = (random_bytes[0].ord | 0x80).chr
38
38
 
39
- bn = OpenSSL::BN.new(random_bytes, 2)
40
- bn
39
+ OpenSSL::BN.new(random_bytes, 2)
41
40
  end
42
41
  end
43
42
  end
@@ -22,22 +22,22 @@ module MTProto
22
22
  raise 'Invalid g: must be 2, 3, 4, 5, 6, or 7' unless [2, 3, 4, 5, 6, 7].include?(g)
23
23
 
24
24
  p_mod = case g
25
- when 2
26
- dh_prime % 8 == 7
27
- when 3
28
- dh_prime % 3 == 2
29
- when 4
30
- true
31
- when 5
32
- mod5 = dh_prime % 5
33
- mod5 == 1 || mod5 == 4
34
- when 6
35
- mod24 = dh_prime % 24
36
- mod24 == 19 || mod24 == 23
37
- when 7
38
- mod7 = dh_prime % 7
39
- [3, 5, 6].include?(mod7)
40
- end
25
+ when 2
26
+ dh_prime % 8 == 7
27
+ when 3
28
+ dh_prime % 3 == 2
29
+ when 4
30
+ true
31
+ when 5
32
+ mod5 = dh_prime % 5
33
+ [1, 4].include?(mod5)
34
+ when 6
35
+ mod24 = dh_prime % 24
36
+ [19, 23].include?(mod24)
37
+ when 7
38
+ mod7 = dh_prime % 7
39
+ [3, 5, 6].include?(mod7)
40
+ end
41
41
 
42
42
  raise "g=#{g} is not a valid generator for this prime" unless p_mod
43
43
 
@@ -52,9 +52,7 @@ module MTProto
52
52
  min_prime = OpenSSL::BN.new(2)**2047
53
53
  max_prime = OpenSSL::BN.new(2)**2048
54
54
 
55
- if dh_prime <= min_prime || dh_prime >= max_prime
56
- raise 'dh_prime out of range (must be 2^2047 < p < 2^2048)'
57
- end
55
+ raise 'dh_prime out of range (must be 2^2047 < p < 2^2048)' if dh_prime <= min_prime || dh_prime >= max_prime
58
56
 
59
57
  true
60
58
  end
@@ -29,7 +29,7 @@ module MTProto
29
29
 
30
30
  while y < x
31
31
  x = y
32
- y = (x + n / x) / 2
32
+ y = (x + (n / x)) / 2
33
33
  end
34
34
 
35
35
  x
@@ -9,7 +9,7 @@ module MTProto
9
9
  attr_reader :key, :fingerprint
10
10
 
11
11
  def initialize(pem_string)
12
- raise ArgumentError, "pem_string is required" if pem_string.nil? || pem_string.empty?
12
+ raise ArgumentError, 'pem_string is required' if pem_string.nil? || pem_string.empty?
13
13
 
14
14
  @key = OpenSSL::PKey::RSA.new(pem_string)
15
15
  @fingerprint = calculate_fingerprint
@@ -20,7 +20,7 @@ module MTProto
20
20
  end
21
21
 
22
22
  def self.find_by_fingerprint(fingerprints, public_key)
23
- raise ArgumentError, "public_key is required" if public_key.nil? || public_key.empty?
23
+ raise ArgumentError, 'public_key is required' if public_key.nil? || public_key.empty?
24
24
 
25
25
  key = new(public_key)
26
26
  key if fingerprints.include?(key.fingerprint)
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'securerandom'
5
+
6
+ module MTProto
7
+ module Crypto
8
+ module SRP
9
+ SIZE_FOR_HASH = 256
10
+
11
+ module_function
12
+
13
+ def compute_check(algo:, srp_b:, srp_id:, password:)
14
+ salt1 = algo[:salt1]
15
+ salt2 = algo[:salt2]
16
+ g = algo[:g]
17
+ p_bytes = algo[:p]
18
+
19
+ p_int = bytes_to_int(p_bytes)
20
+ g_int = g
21
+ b_int = bytes_to_int(srp_b)
22
+
23
+ pw_hash = compute_hash(salt1, salt2, password)
24
+ x = bytes_to_int(pw_hash)
25
+
26
+ p_for_hash = pad_to_256(p_bytes)
27
+ g_for_hash = int_to_256_bytes(g_int)
28
+ b_for_hash = pad_to_256(srp_b)
29
+
30
+ g_x = mod_pow(g_int, x, p_int)
31
+ k = bytes_to_int(sha256(p_for_hash, g_for_hash))
32
+ kg_x = (k * g_x) % p_int
33
+
34
+ a_int, a_for_hash, u = generate_and_check_random(g_int, p_int, b_for_hash)
35
+
36
+ g_b = (b_int - kg_x) % p_int
37
+ ux = u * x
38
+ a_ux = a_int + ux
39
+ s_int = mod_pow(g_b, a_ux, p_int)
40
+ k_key = sha256(int_to_256_bytes(s_int))
41
+
42
+ m1 = sha256(
43
+ xor(sha256(p_for_hash), sha256(g_for_hash)),
44
+ sha256(salt1),
45
+ sha256(salt2),
46
+ a_for_hash,
47
+ b_for_hash,
48
+ k_key
49
+ )
50
+
51
+ { srp_id: srp_id, a: a_for_hash, m1: m1 }
52
+ end
53
+
54
+ def compute_hash(salt1, salt2, password)
55
+ hash1 = sha256(salt1, password.encode('utf-8'), salt1)
56
+ hash2 = sha256(salt2, hash1, salt2)
57
+ hash3 = pbkdf2_sha512(hash2, salt1, 100_000)
58
+ sha256(salt2, hash3, salt2)
59
+ end
60
+
61
+ def sha256(*parts)
62
+ digest = OpenSSL::Digest.new('SHA256')
63
+ parts.each { |p| digest.update(p) }
64
+ digest.digest
65
+ end
66
+
67
+ def pbkdf2_sha512(password, salt, iterations)
68
+ OpenSSL::KDF.pbkdf2_hmac(
69
+ password,
70
+ salt: salt,
71
+ iterations: iterations,
72
+ length: 64,
73
+ hash: 'SHA512'
74
+ )
75
+ end
76
+
77
+ def mod_pow(base, exp, mod)
78
+ base.to_bn.mod_exp(exp.to_bn, mod.to_bn).to_i
79
+ end
80
+
81
+ def generate_and_check_random(g, p, b_for_hash)
82
+ loop do
83
+ random = SecureRandom.random_bytes(256)
84
+ a_int = bytes_to_int(random)
85
+ a_big = mod_pow(g, a_int, p)
86
+ a_for_hash = int_to_256_bytes(a_big)
87
+ u = bytes_to_int(sha256(a_for_hash, b_for_hash))
88
+ next if u.zero?
89
+
90
+ return [a_int, a_for_hash, u]
91
+ end
92
+ end
93
+
94
+ def pad_to_256(bytes)
95
+ bytes = bytes.b
96
+ return bytes if bytes.bytesize >= SIZE_FOR_HASH
97
+
98
+ ("\x00" * (SIZE_FOR_HASH - bytes.bytesize)).b + bytes
99
+ end
100
+
101
+ def int_to_256_bytes(num)
102
+ hex = num.to_s(16)
103
+ hex = "0#{hex}" if hex.length.odd?
104
+ raw = [hex].pack('H*')
105
+ pad_to_256(raw)
106
+ end
107
+
108
+ def bytes_to_int(bytes)
109
+ bytes.unpack1('H*').to_i(16)
110
+ end
111
+
112
+ def xor(a, b)
113
+ a.bytes.zip(b.bytes).map { |x, y| x ^ y }.pack('C*')
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DelegateMethods
4
+ def delegate(*methods, to:)
5
+ methods.each do |method|
6
+ define_method(method) do |*args, **kwargs, &block|
7
+ instance_variable_get(:"@#{to}").public_send(method, *args, **kwargs, &block)
8
+ end
9
+ end
10
+ end
11
+ end