mtproto 0.0.7 → 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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +4 -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 +66 -103
  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_updates_difference.rb +21 -0
  10. data/lib/mtproto/client/api/get_updates_state.rb +14 -0
  11. data/lib/mtproto/client/api/get_users.rb +14 -0
  12. data/lib/mtproto/client/api/import_login_token.rb +23 -0
  13. data/lib/mtproto/client/api/send_code.rb +21 -0
  14. data/lib/mtproto/client/api/sign_in.rb +27 -0
  15. data/lib/mtproto/client/api.rb +28 -0
  16. data/lib/mtproto/client/rpc/response.rb +63 -0
  17. data/lib/mtproto/client/rpc.rb +67 -107
  18. data/lib/mtproto/client.rb +176 -31
  19. data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
  20. data/lib/mtproto/crypto/dh_validator.rb +17 -19
  21. data/lib/mtproto/crypto/factorization.rb +1 -1
  22. data/lib/mtproto/crypto/rsa_key.rb +2 -2
  23. data/lib/mtproto/crypto/srp.rb +117 -0
  24. data/lib/mtproto/delegate_methods.rb +11 -0
  25. data/lib/mtproto/message/message.rb +85 -0
  26. data/lib/mtproto/session.rb +1 -1
  27. data/lib/mtproto/tl/constructors.rb +2269 -0
  28. data/lib/mtproto/tl/object.rb +25 -0
  29. data/lib/mtproto/tl/objects/account_password.rb +72 -0
  30. data/lib/mtproto/tl/objects/authorization.rb +73 -0
  31. data/lib/mtproto/tl/objects/check_password.rb +46 -0
  32. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +47 -0
  33. data/lib/mtproto/tl/objects/dh_gen_response.rb +50 -0
  34. data/lib/mtproto/tl/objects/export_login_token.rb +51 -0
  35. data/lib/mtproto/tl/objects/get_config.rb +15 -0
  36. data/lib/mtproto/tl/objects/get_difference.rb +36 -0
  37. data/lib/mtproto/tl/objects/get_password.rb +15 -0
  38. data/lib/mtproto/tl/objects/get_state.rb +15 -0
  39. data/lib/mtproto/tl/objects/get_users.rb +20 -0
  40. data/lib/mtproto/tl/objects/help_config.rb +77 -0
  41. data/lib/mtproto/tl/objects/import_login_token.rb +39 -0
  42. data/lib/mtproto/tl/objects/init_connection.rb +59 -0
  43. data/lib/mtproto/tl/objects/invoke_with_layer.rb +22 -0
  44. data/lib/mtproto/tl/objects/login_token.rb +82 -0
  45. data/lib/mtproto/tl/objects/pq_inner_data.rb +69 -0
  46. data/lib/mtproto/tl/objects/req_dh_params.rb +65 -0
  47. data/lib/mtproto/tl/objects/req_pq_multi.rb +23 -0
  48. data/lib/mtproto/tl/objects/res_pq.rb +75 -0
  49. data/lib/mtproto/tl/objects/send_code.rb +50 -0
  50. data/lib/mtproto/tl/objects/sent_code.rb +79 -0
  51. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
  52. data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
  53. data/lib/mtproto/tl/objects/set_client_dh_params.rb +48 -0
  54. data/lib/mtproto/tl/objects/sign_in.rb +47 -0
  55. data/lib/mtproto/tl/objects/update.rb +80 -0
  56. data/lib/mtproto/tl/objects/update_short.rb +22 -0
  57. data/lib/mtproto/tl/objects/update_short_message.rb +67 -0
  58. data/lib/mtproto/tl/objects/updates_difference.rb +157 -0
  59. data/lib/mtproto/tl/objects/updates_state.rb +37 -0
  60. data/lib/mtproto/tl/objects/users.rb +86 -0
  61. data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
  62. data/lib/mtproto/transport/connection.rb +23 -0
  63. data/lib/mtproto/transport/errors.rb +11 -0
  64. data/lib/mtproto/transport/packet.rb +19 -0
  65. data/lib/mtproto/transport/tcp_connection.rb +57 -46
  66. data/lib/mtproto/{tl → type}/bad_msg_notification.rb +11 -11
  67. data/lib/mtproto/{tl → type}/client_dh_inner_data.rb +1 -1
  68. data/lib/mtproto/{tl → type}/gzip_packed.rb +6 -4
  69. data/lib/mtproto/{tl → type}/message.rb +3 -3
  70. data/lib/mtproto/{tl → type}/msg_container.rb +1 -1
  71. data/lib/mtproto/{tl → type}/new_session_created.rb +1 -1
  72. data/lib/mtproto/{tl/p_q_inner_data.rb → type/pq_inner_data.rb} +1 -1
  73. data/lib/mtproto/{tl → type}/rpc_error.rb +1 -2
  74. data/lib/mtproto/{tl → type}/serializer.rb +1 -1
  75. data/lib/mtproto/{tl → type}/server_dh_inner_data.rb +1 -1
  76. data/lib/mtproto/updates_poller.rb +37 -33
  77. data/lib/mtproto/version.rb +1 -1
  78. data/lib/mtproto.rb +21 -22
  79. data/scripts/generate_constructors.rb +65 -0
  80. metadata +80 -49
  81. data/lib/mtproto/rpc/get_config.rb +0 -37
  82. data/lib/mtproto/rpc/get_contacts.rb +0 -22
  83. data/lib/mtproto/rpc/get_updates_difference.rb +0 -33
  84. data/lib/mtproto/rpc/get_updates_state.rb +0 -22
  85. data/lib/mtproto/rpc/get_users.rb +0 -22
  86. data/lib/mtproto/rpc/ping.rb +0 -26
  87. data/lib/mtproto/rpc/send_code.rb +0 -44
  88. data/lib/mtproto/rpc/send_message.rb +0 -31
  89. data/lib/mtproto/rpc/sign_in.rb +0 -52
  90. data/lib/mtproto/tl/auth_key/dh_gen_response.rb +0 -37
  91. data/lib/mtproto/tl/auth_key/req_dh_params.rb +0 -31
  92. data/lib/mtproto/tl/auth_key/req_pq_multi.rb +0 -18
  93. data/lib/mtproto/tl/auth_key/res_pq.rb +0 -62
  94. data/lib/mtproto/tl/auth_key/server_dh_params.rb +0 -43
  95. data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +0 -25
  96. data/lib/mtproto/tl/code_settings.rb +0 -25
  97. data/lib/mtproto/tl/config.rb +0 -124
  98. data/lib/mtproto/tl/method_builder.rb +0 -29
  99. data/lib/mtproto/tl/rpc/auth/authorization.rb +0 -107
  100. data/lib/mtproto/tl/rpc/auth/send_code.rb +0 -28
  101. data/lib/mtproto/tl/rpc/auth/sent_code.rb +0 -36
  102. data/lib/mtproto/tl/rpc/auth/sign_in.rb +0 -32
  103. data/lib/mtproto/tl/rpc/contacts/contacts.rb +0 -155
  104. data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +0 -18
  105. data/lib/mtproto/tl/rpc/help/config.rb +0 -35
  106. data/lib/mtproto/tl/rpc/help/get_config.rb +0 -17
  107. data/lib/mtproto/tl/rpc/messages/send_message.rb +0 -43
  108. data/lib/mtproto/tl/rpc/messages/updates.rb +0 -87
  109. data/lib/mtproto/tl/rpc/ping.rb +0 -18
  110. data/lib/mtproto/tl/rpc/pong.rb +0 -46
  111. data/lib/mtproto/tl/rpc/updates/difference.rb +0 -332
  112. data/lib/mtproto/tl/rpc/updates/get_difference.rb +0 -42
  113. data/lib/mtproto/tl/rpc/updates/get_state.rb +0 -17
  114. data/lib/mtproto/tl/rpc/updates/state.rb +0 -59
  115. data/lib/mtproto/tl/rpc/users/get_users.rb +0 -25
  116. data/lib/mtproto/tl/rpc/users/users.rb +0 -99
  117. data/lib/mtproto/tl/sent_code.rb +0 -128
@@ -1,140 +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)
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
23
28
 
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
- )
29
+ response
30
+ end
32
31
 
33
- @client.connection.send(encrypted_msg.serialize)
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<')
34
35
 
35
- response_data = @client.connection.recv(timeout: @client.timeout)
36
+ client.update_server_salt(new_server_salt)
36
37
 
37
- decrypted = EncryptedMessage.decrypt(
38
- auth_key: @client.auth_key,
39
- encrypted_message_data: response_data,
40
- sender: :server
41
- )
38
+ response = @pending_requests.delete(bad_msg_id)
39
+ return unless response
42
40
 
43
- response_body = decrypted[:body]
44
-
45
- constructor = response_body[0, 4].unpack1('L<')
46
-
47
- # Handle bad_server_salt
48
- if constructor == CONSTRUCTOR_BAD_SERVER_SALT
49
- # bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long
50
- offset = 4
51
- bad_msg_id = response_body[offset, 8].unpack1('Q<')
52
- offset += 8
53
- bad_msg_seqno = response_body[offset, 4].unpack1('L<')
54
- offset += 4
55
- error_code = response_body[offset, 4].unpack1('L<')
56
- offset += 4
57
- new_server_salt = response_body[offset, 8].unpack1('Q<')
58
-
59
- # Update server salt and retry
60
- update_server_salt(new_server_salt)
61
- return call(body, content_related: content_related)
62
- end
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
63
45
 
64
- if constructor == TL::NewSessionCreated::CONSTRUCTOR
65
- session_info = TL::NewSessionCreated.deserialize(response_body)
66
- update_server_salt(session_info.server_salt)
67
-
68
- response_data = @client.connection.recv(timeout: @client.timeout)
69
- decrypted = EncryptedMessage.decrypt(
70
- auth_key: @client.auth_key,
71
- encrypted_message_data: response_data,
72
- sender: :server
73
- )
74
- response_body = decrypted[:body]
75
- constructor = response_body[0, 4].unpack1('L<')
76
- end
46
+ def handle_rpc_result(response_body)
47
+ req_msg_id = response_body[4, 8].unpack1('Q<')
48
+ result = response_body[12..]
77
49
 
78
- if constructor == TL::MsgContainer::CONSTRUCTOR
79
- container = TL::MsgContainer.deserialize(response_body)
80
-
81
- rpc_result = container.messages.find do |msg|
82
- msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
83
- end
84
-
85
- return rpc_result[:body][12..] if rpc_result
86
-
87
- new_session = container.messages.find do |msg|
88
- msg[:body][0, 4].unpack1('L<') == TL::NewSessionCreated::CONSTRUCTOR
89
- end
90
- if new_session
91
- session_info = TL::NewSessionCreated.deserialize(new_session[:body])
92
- update_server_salt(session_info.server_salt)
93
-
94
- other_messages = container.messages.reject do |msg|
95
- constructor = msg[:body][0, 4].unpack1('L<')
96
- (
97
- constructor == TL::NewSessionCreated::CONSTRUCTOR ||
98
- constructor == CONSTRUCTOR_MSGS_ACK
99
- )
100
- end
101
-
102
- return other_messages.first[:body] unless other_messages.empty?
103
-
104
- response_data = @client.connection.recv(timeout: @client.timeout)
105
- decrypted = EncryptedMessage.decrypt(
106
- auth_key: @client.auth_key,
107
- encrypted_message_data: response_data,
108
- sender: :server
109
- )
110
- response_body = decrypted[:body]
111
- constructor = response_body[0, 4].unpack1('L<')
112
-
113
- return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
114
-
115
- if constructor == TL::MsgContainer::CONSTRUCTOR
116
- container = TL::MsgContainer.deserialize(response_body)
117
- rpc_result = container.messages.find do |msg|
118
- msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
119
- end
120
- return rpc_result[:body][12..] if rpc_result
121
- end
122
-
123
- return response_body
124
- end
125
-
126
- return container.messages.first[:body]
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<')
127
54
  end
128
55
 
129
- return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
56
+ response = @pending_requests.delete(req_msg_id)
57
+ return unless response
58
+
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)
64
+ end
65
+ end
130
66
 
131
- response_body
67
+ def signal_all_error(error)
68
+ @pending_requests.each_value { |response| response.signal_error(error) }
69
+ @pending_requests.clear
132
70
  end
133
71
 
134
72
  private
135
73
 
136
- def update_server_salt(server_salt)
137
- @client.instance_variable_set(:@server_salt, server_salt)
74
+ def serialize_request(request)
75
+ if request.respond_to?(:body)
76
+ request.body.pack('C*')
77
+ else
78
+ request
79
+ end
80
+ end
81
+
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
138
98
  end
139
99
  end
140
100
  end
@@ -3,25 +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 'tl/message'
11
+ require_relative 'type/message'
9
12
  require_relative 'client/rpc'
10
- require_relative 'tl/method_builder'
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'
11
18
 
12
19
  module MTProto
13
20
  class Client
14
- include TL::MethodBuilder
21
+ extend DelegateMethods
15
22
 
16
- attr_reader :connection, :server_key, :auth_key, :server_salt, :time_offset, :session, :timeout, :user_id, :access_hash, :dc_number
17
- attr_accessor :api_id, :api_hash, :device_model, :system_version, :app_version, :system_lang_code, :lang_pack, :lang_code
23
+ API_LAYER = 214
18
24
 
19
- def initialize(api_id: nil, api_hash: nil, host:, port: 443, public_key: nil, dc_number: nil, test_mode: false, timeout: 10)
20
- raise ArgumentError, "host is required" if host.nil? || host.empty?
21
- 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
22
29
 
23
- codec = Transport::AbridgedPacketCodec.new
24
- @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)
25
59
  @public_key = public_key
26
60
  @dc_number = dc_number
27
61
  @test_mode = test_mode
@@ -35,29 +69,76 @@ module MTProto
35
69
  @user_id = nil
36
70
  @access_hash = nil
37
71
 
38
- # Client configuration defaults
39
- @api_id = api_id || 0
40
- @api_hash = api_hash || ''
41
72
  @device_model = 'Ruby MTProto'
42
73
  @system_version = RUBY_DESCRIPTION
43
74
  @app_version = '0.1.0'
44
75
  @system_lang_code = 'en'
45
76
  @lang_pack = ''
46
77
  @lang_code = 'en'
78
+
79
+ @receiver_task = nil
80
+ @running = false
81
+ @on_update_callbacks = []
47
82
  end
48
83
 
49
- def connect!
50
- @connection.connect!
84
+ def on_update(&block)
85
+ @on_update_callbacks << block
86
+ end
87
+
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
51
120
  end
52
121
 
53
122
  def disconnect!
54
- @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?
55
130
  end
56
131
 
57
132
  def exchange_keys!
58
- raise ArgumentError, "public_key is required for auth key generation" if @public_key.nil? || @public_key.empty?
59
-
60
- 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
+ )
61
142
  result = generator.generate
62
143
 
63
144
  @auth_key = generator.auth_key
@@ -72,8 +153,37 @@ module MTProto
72
153
  @rpc ||= RPC.new(self)
73
154
  end
74
155
 
75
- def save_session
76
- raise 'Cannot save session: auth_key not set' unless @auth_key
156
+ def api
157
+ @api ||= API.new(self)
158
+ end
159
+
160
+ def init_connection!
161
+ return if @connection_initialized
162
+
163
+ query = TL::InvokeWithLayer.new(
164
+ layer: API_LAYER,
165
+ query: TL::InitConnection.new(
166
+ api_id: api_id,
167
+ device_model: device_model,
168
+ system_version: system_version,
169
+ app_version: app_version,
170
+ system_lang_code: system_lang_code,
171
+ lang_pack: lang_pack,
172
+ lang_code: lang_code,
173
+ query: TL::GetConfig.new
174
+ )
175
+ )
176
+
177
+ response = rpc.call(query, TL::HelpConfig)
178
+ response.wait!(timeout)
179
+
180
+ @connection_initialized = true
181
+
182
+ response.body
183
+ end
184
+
185
+ def save_auth_data
186
+ raise 'Cannot save auth_data: auth_key not set' unless @auth_key
77
187
 
78
188
  {
79
189
  auth_key: Base64.strict_encode64(@auth_key),
@@ -85,18 +195,17 @@ module MTProto
85
195
  }
86
196
  end
87
197
 
88
- def load_session(session_data)
89
- raise ArgumentError, 'session_data must be a Hash' unless session_data.is_a?(Hash)
90
- raise ArgumentError, 'auth_key is required' unless session_data[:auth_key]
91
- raise ArgumentError, 'server_salt is required' unless session_data[:server_salt]
198
+ def load_auth_data(auth_data)
199
+ raise ArgumentError, 'auth_data must be a Hash' unless auth_data.is_a?(Hash)
200
+ raise ArgumentError, 'auth_key is required' unless auth_data[:auth_key]
201
+ raise ArgumentError, 'server_salt is required' unless auth_data[:server_salt]
92
202
 
93
- @auth_key = Base64.strict_decode64(session_data[:auth_key])
94
- @server_salt = session_data[:server_salt]
95
- @time_offset = session_data[:time_offset] || 0
96
- @user_id = session_data[:user_id]
97
- @access_hash = session_data[:access_hash]
203
+ @auth_key = Base64.strict_decode64(auth_data[:auth_key])
204
+ @server_salt = auth_data[:server_salt]
205
+ @time_offset = auth_data[:time_offset] || 0
206
+ @user_id = auth_data[:user_id]
207
+ @access_hash = auth_data[:access_hash]
98
208
 
99
- # Create a fresh session for the new connection
100
209
  @session = Session.new
101
210
 
102
211
  true
@@ -106,5 +215,41 @@ module MTProto
106
215
  @user_id = user_id
107
216
  @access_hash = access_hash
108
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
109
254
  end
110
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