mtproto 0.0.8 → 0.0.9

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/ext/aes_ige/extconf.rb +3 -9
  3. data/ext/factorization/extconf.rb +2 -0
  4. data/lib/mtproto/auth_key_generator.rb +68 -105
  5. data/lib/mtproto/binary.rb +21 -0
  6. data/lib/mtproto/client/api/check_password.rb +41 -0
  7. data/lib/mtproto/client/api/export_login_token.rb +27 -0
  8. data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
  9. data/lib/mtproto/client/api/get_updates_state.rb +14 -0
  10. data/lib/mtproto/client/api/get_users.rb +14 -0
  11. data/lib/mtproto/client/api/import_login_token.rb +23 -0
  12. data/lib/mtproto/client/api/send_code.rb +21 -0
  13. data/lib/mtproto/client/api/sign_in.rb +27 -0
  14. data/lib/mtproto/client/api.rb +28 -0
  15. data/lib/mtproto/client/rpc/response.rb +63 -0
  16. data/lib/mtproto/client/rpc.rb +61 -125
  17. data/lib/mtproto/client.rb +142 -31
  18. data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
  19. data/lib/mtproto/crypto/dh_validator.rb +17 -19
  20. data/lib/mtproto/crypto/factorization.rb +1 -1
  21. data/lib/mtproto/crypto/rsa_key.rb +2 -2
  22. data/lib/mtproto/crypto/srp.rb +117 -0
  23. data/lib/mtproto/delegate_methods.rb +11 -0
  24. data/lib/mtproto/message/message.rb +85 -0
  25. data/lib/mtproto/session.rb +1 -1
  26. data/lib/mtproto/tl/constructors.rb +2269 -0
  27. data/lib/mtproto/tl/object.rb +25 -0
  28. data/lib/mtproto/tl/objects/account_password.rb +72 -0
  29. data/lib/mtproto/tl/objects/authorization.rb +73 -0
  30. data/lib/mtproto/tl/objects/check_password.rb +46 -0
  31. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +47 -0
  32. data/lib/mtproto/tl/objects/dh_gen_response.rb +50 -0
  33. data/lib/mtproto/tl/objects/export_login_token.rb +51 -0
  34. data/lib/mtproto/tl/objects/get_config.rb +15 -0
  35. data/lib/mtproto/tl/objects/get_difference.rb +36 -0
  36. data/lib/mtproto/tl/objects/get_password.rb +15 -0
  37. data/lib/mtproto/tl/objects/get_state.rb +15 -0
  38. data/lib/mtproto/tl/objects/get_users.rb +20 -0
  39. data/lib/mtproto/tl/objects/help_config.rb +77 -0
  40. data/lib/mtproto/tl/objects/import_login_token.rb +39 -0
  41. data/lib/mtproto/tl/objects/init_connection.rb +59 -0
  42. data/lib/mtproto/tl/objects/invoke_with_layer.rb +22 -0
  43. data/lib/mtproto/tl/objects/login_token.rb +82 -0
  44. data/lib/mtproto/tl/objects/pq_inner_data.rb +69 -0
  45. data/lib/mtproto/tl/objects/req_dh_params.rb +65 -0
  46. data/lib/mtproto/tl/objects/req_pq_multi.rb +23 -0
  47. data/lib/mtproto/tl/objects/res_pq.rb +75 -0
  48. data/lib/mtproto/tl/objects/send_code.rb +50 -0
  49. data/lib/mtproto/tl/objects/sent_code.rb +79 -0
  50. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
  51. data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
  52. data/lib/mtproto/tl/objects/set_client_dh_params.rb +48 -0
  53. data/lib/mtproto/tl/objects/sign_in.rb +47 -0
  54. data/lib/mtproto/tl/objects/update.rb +80 -0
  55. data/lib/mtproto/tl/objects/update_short.rb +22 -0
  56. data/lib/mtproto/tl/objects/update_short_message.rb +67 -0
  57. data/lib/mtproto/tl/objects/updates_difference.rb +157 -0
  58. data/lib/mtproto/tl/objects/updates_state.rb +37 -0
  59. data/lib/mtproto/tl/objects/users.rb +86 -0
  60. data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
  61. data/lib/mtproto/transport/connection.rb +23 -0
  62. data/lib/mtproto/transport/errors.rb +11 -0
  63. data/lib/mtproto/transport/packet.rb +19 -0
  64. data/lib/mtproto/transport/tcp_connection.rb +57 -46
  65. data/lib/mtproto/type/bad_msg_notification.rb +10 -10
  66. data/lib/mtproto/type/gzip_packed.rb +5 -3
  67. data/lib/mtproto/type/message.rb +2 -2
  68. data/lib/mtproto/type/rpc_error.rb +0 -1
  69. data/lib/mtproto/updates_poller.rb +37 -33
  70. data/lib/mtproto/version.rb +1 -1
  71. data/lib/mtproto.rb +11 -17
  72. data/scripts/generate_constructors.rb +65 -0
  73. metadata +62 -51
  74. data/lib/mtproto/async/middleware/base.rb +0 -17
  75. data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
  76. data/lib/mtproto/async/request.rb +0 -18
  77. data/lib/mtproto/async/request_queue.rb +0 -63
  78. data/lib/mtproto/async_client.rb +0 -201
  79. data/lib/mtproto/rpc/get_config.rb +0 -34
  80. data/lib/mtproto/rpc/get_contacts.rb +0 -29
  81. data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
  82. data/lib/mtproto/rpc/get_updates_state.rb +0 -29
  83. data/lib/mtproto/rpc/get_users.rb +0 -29
  84. data/lib/mtproto/rpc/ping.rb +0 -33
  85. data/lib/mtproto/rpc/send_code.rb +0 -41
  86. data/lib/mtproto/rpc/send_message.rb +0 -47
  87. data/lib/mtproto/rpc/sign_in.rb +0 -48
  88. data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
  89. data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
  90. data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
  91. data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
  92. data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
  93. data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
  94. data/lib/mtproto/type/code_settings.rb +0 -25
  95. data/lib/mtproto/type/config.rb +0 -124
  96. data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
  97. data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
  98. data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
  99. data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
  100. data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
  101. data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
  102. data/lib/mtproto/type/rpc/help/config.rb +0 -35
  103. data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
  104. data/lib/mtproto/type/rpc/init_connection.rb +0 -28
  105. data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
  106. data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
  107. data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
  108. data/lib/mtproto/type/rpc/ping.rb +0 -18
  109. data/lib/mtproto/type/rpc/pong.rb +0 -46
  110. data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
  111. data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
  112. data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
  113. data/lib/mtproto/type/rpc/updates/state.rb +0 -59
  114. data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
  115. data/lib/mtproto/type/rpc/users/users.rb +0 -99
  116. data/lib/mtproto/type/sent_code.rb +0 -128
@@ -1,164 +1,100 @@
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
9
  CONSTRUCTOR_RPC_RESULT = 0xf35c6d01
9
- CONSTRUCTOR_MSGS_ACK = 0x62d6b459
10
10
  CONSTRUCTOR_BAD_SERVER_SALT = 0xedab447b
11
11
 
12
+ attr_reader :pending_requests
13
+
12
14
  def initialize(client)
13
15
  @client = client
14
- @connection_initialized = false
16
+ @pending_requests = {}
15
17
  end
16
18
 
17
- def call(body, content_related: true)
18
- raise 'Auth key not generated' unless @client.auth_key
19
+ def call(request, response_class)
20
+ raise 'Mainloop not running' unless @client.mainloop_running?
21
+ raise 'Auth key not generated' unless @client.auth_key?
19
22
  raise 'Session not initialized' unless @client.session
20
23
 
21
- msg_id = MessageId.generate(time_offset: @client.time_offset)
22
- seq_no = @client.session.next_seq_no(content_related: content_related)
23
-
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)
24
+ body = serialize_request(request)
25
+ msg_id = send_encrypted(body)
26
+ response = Response.new(msg_id, response_class, body)
27
+ @pending_requests[msg_id] = response
34
28
 
35
- msg_id
29
+ response
36
30
  end
37
31
 
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
- )
32
+ def handle_bad_server_salt(response_body, client)
33
+ bad_msg_id = response_body[4, 8].unpack1('Q<')
34
+ new_server_salt = response_body[20, 8].unpack1('Q<')
53
35
 
54
- @client.connection.send(encrypted_msg.serialize)
36
+ client.update_server_salt(new_server_salt)
55
37
 
56
- response_data = @client.connection.recv(timeout: @client.timeout)
38
+ response = @pending_requests.delete(bad_msg_id)
39
+ return unless response
57
40
 
58
- decrypted = EncryptedMessage.decrypt(
59
- auth_key: @client.auth_key,
60
- encrypted_message_data: response_data,
61
- sender: :server
62
- )
63
-
64
- response_body = decrypted[:body]
65
-
66
- constructor = response_body[0, 4].unpack1('L<')
41
+ new_msg_id = send_encrypted(response.body_bytes)
42
+ response.instance_variable_set(:@msg_id, new_msg_id)
43
+ @pending_requests[new_msg_id] = response
44
+ end
67
45
 
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<')
46
+ def handle_rpc_result(response_body)
47
+ req_msg_id = response_body[4, 8].unpack1('Q<')
48
+ result = response_body[12..]
74
49
 
75
- update_server_salt(new_server_salt)
76
- return call_sync(body, content_related: content_related)
50
+ result_constructor = result[0, 4].unpack1('L<')
51
+ if result_constructor == Type::GzipPacked::CONSTRUCTOR
52
+ result = Type::GzipPacked.unpack(result)
53
+ result_constructor = result[0, 4].unpack1('L<')
77
54
  end
78
55
 
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
56
+ response = @pending_requests.delete(req_msg_id)
57
+ return unless response
92
58
 
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]
59
+ if result_constructor == Type::RpcError::CONSTRUCTOR
60
+ error = Type::RpcError.deserialize(result)
61
+ response.signal_error(RpcError.new(error.error_code, error.error_message))
62
+ else
63
+ response.signal(result)
142
64
  end
65
+ end
143
66
 
144
- return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
145
-
146
- response_body
67
+ def signal_all_error(error)
68
+ @pending_requests.each_value { |response| response.signal_error(error) }
69
+ @pending_requests.clear
147
70
  end
148
71
 
149
72
  private
150
73
 
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)
74
+ def serialize_request(request)
75
+ if request.respond_to?(:body)
76
+ request.body.pack('C*')
77
+ else
78
+ request
156
79
  end
157
- result
158
80
  end
159
81
 
160
- def update_server_salt(server_salt)
161
- @client.instance_variable_set(:@server_salt, server_salt)
82
+ def send_encrypted(body, content_related: true)
83
+ msg_id = MessageId.generate(time_offset: @client.time_offset)
84
+ seq_no = @client.session.next_seq_no(content_related: content_related)
85
+
86
+ encrypted_msg = EncryptedMessage.encrypt(
87
+ auth_key: @client.auth_key,
88
+ server_salt: @client.server_salt,
89
+ session_id: @client.session.session_id,
90
+ msg_id: msg_id,
91
+ seq_no: seq_no,
92
+ body: body
93
+ )
94
+
95
+ @client.connection.send(Transport::Packet.new(encrypted_msg.serialize.bytes))
96
+
97
+ msg_id
162
98
  end
163
99
  end
164
100
  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
11
  require_relative 'type/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 RPC::CONSTRUCTOR_BAD_SERVER_SALT
230
+ rpc.handle_bad_server_salt(response_body, self)
231
+ when Type::NewSessionCreated::CONSTRUCTOR
232
+ handle_new_session(response_body)
233
+ when Type::MsgContainer::CONSTRUCTOR
234
+ handle_container(response_body)
235
+ when RPC::CONSTRUCTOR_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 = Type::NewSessionCreated.deserialize(response_body)
244
+ @server_salt = session_info.server_salt
245
+ end
246
+
247
+ def handle_container(response_body)
248
+ container = Type::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