mtproto 0.0.5 → 0.0.7

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/Rakefile +13 -0
  4. data/lib/mtproto/auth_key_generator.rb +36 -13
  5. data/lib/mtproto/client/rpc.rb +141 -0
  6. data/lib/mtproto/client.rb +60 -185
  7. data/lib/mtproto/crypto/aes_ige.rb +1 -1
  8. data/lib/mtproto/crypto/factorization.rb +1 -1
  9. data/lib/mtproto/crypto/rsa_key.rb +9 -15
  10. data/lib/mtproto/errors.rb +33 -0
  11. data/lib/mtproto/message_id.rb +13 -0
  12. data/lib/mtproto/rpc/get_config.rb +37 -0
  13. data/lib/mtproto/rpc/get_contacts.rb +22 -0
  14. data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
  15. data/lib/mtproto/rpc/get_updates_state.rb +22 -0
  16. data/lib/mtproto/rpc/get_users.rb +22 -0
  17. data/lib/mtproto/rpc/ping.rb +26 -0
  18. data/lib/mtproto/rpc/send_code.rb +44 -0
  19. data/lib/mtproto/rpc/send_message.rb +31 -0
  20. data/lib/mtproto/rpc/sign_in.rb +52 -0
  21. data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
  22. data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
  23. data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
  24. data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
  25. data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
  26. data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
  27. data/lib/mtproto/tl/code_settings.rb +25 -0
  28. data/lib/mtproto/tl/config.rb +4 -2
  29. data/lib/mtproto/tl/gzip_packed.rb +1 -1
  30. data/lib/mtproto/tl/message.rb +8 -216
  31. data/lib/mtproto/tl/method_builder.rb +29 -0
  32. data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
  33. data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
  34. data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
  35. data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
  36. data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
  37. data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
  38. data/lib/mtproto/tl/rpc/help/config.rb +35 -0
  39. data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
  40. data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
  41. data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
  42. data/lib/mtproto/tl/rpc/ping.rb +18 -0
  43. data/lib/mtproto/tl/rpc/pong.rb +46 -0
  44. data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
  45. data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
  46. data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
  47. data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
  48. data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
  49. data/lib/mtproto/tl/rpc/users/users.rb +99 -0
  50. data/lib/mtproto/tl/sent_code.rb +128 -0
  51. data/lib/mtproto/transport/tcp_connection.rb +1 -1
  52. data/lib/mtproto/updates_poller.rb +111 -0
  53. data/lib/mtproto/version.rb +1 -1
  54. data/lib/mtproto.rb +13 -0
  55. metadata +57 -6
  56. data/ext/aes_ige/Makefile +0 -273
  57. data/ext/aes_ige/aes_ige.bundle +0 -0
  58. data/ext/factorization/Makefile +0 -273
  59. data/ext/factorization/factorization.bundle +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd33fd4bcb039dfffa0a6d2c2193e05fdf306a24c8f34a09b197d8aa45dd9117
4
- data.tar.gz: 2061be3cf7378f0b86cff34eae3c34fa025bfdb252f61bc99307489437b9ab63
3
+ metadata.gz: 2744b720c59f90c3e9d18021ce6096a1c8e559dae949fbd6a9b53948c14f8b5c
4
+ data.tar.gz: 3b9000cabc247db305244b279bc58474466e1549cca3efa07ca1202eaa2d7874
5
5
  SHA512:
6
- metadata.gz: cb9c3c9c4d59755b3e4693e475f51295476caaeb932239439ff8e0940484bb8fe0dd3420282fd6d2f807b8da6755737ece84d0bf464bd5554e6190dcdebc3845
7
- data.tar.gz: dad6e992b30ca7a7e9a20f9d027bd6436a6b90ab6721a45cb36b8ff3080d4e58633d1d50aa2a54c41568a048e93f7b2b9badc4fd7ad3fd361416a3b428da1b18
6
+ metadata.gz: 874a17be8f75bb305057d0a0cb2c136441f9e8da883c44a99169ad0975c5b745b4323abc84ab6edb96b72f2c8f500ec40536a1a6724f585730501514a2e7475b
7
+ data.tar.gz: 6a693436a7443f789cb73a6fe6ecebf523ee288bb174b297fdfac4274340c9a5a00544299d08f0558028645e4cf80dbfb0c70c844ec47d2bb6e413589a45a5b2
data/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ TG_API_ID=your_api_id
2
+ TG_API_HASH=your_api_hash
3
+ TG_TEST_DC='111.111.111.111:443'
4
+ TG_TEST_DC_N='1'
5
+ TG_TEST_DC_KEY='DC public key'
data/Rakefile CHANGED
@@ -14,7 +14,20 @@ end
14
14
 
15
15
  RSpec::Core::RakeTask.new(:spec) do |t|
16
16
  t.rspec_opts = '--require spec_helper'
17
+ t.pattern = 'spec/**/*_spec.rb'
18
+ end
19
+
20
+ RSpec::Core::RakeTask.new('spec:unit') do |t|
21
+ t.rspec_opts = '--require spec_helper'
22
+ t.pattern = 'spec/lib/**/*_spec.rb'
23
+ end
24
+
25
+ RSpec::Core::RakeTask.new('spec:integration') do |t|
26
+ t.rspec_opts = '--require spec_helper'
27
+ t.pattern = 'spec/integration/**/*_spec.rb'
17
28
  end
18
29
 
19
30
  task spec: :compile
31
+ task 'spec:unit': :compile
32
+ task 'spec:integration': :compile
20
33
  task default: :spec
@@ -2,13 +2,27 @@
2
2
 
3
3
  require 'securerandom'
4
4
  require 'digest'
5
+ require_relative 'message_id'
6
+ require_relative 'tl/auth_key/req_pq_multi'
7
+ require_relative 'tl/auth_key/res_pq'
8
+ require_relative 'tl/auth_key/req_dh_params'
9
+ require_relative 'tl/auth_key/server_dh_params'
10
+ require_relative 'tl/auth_key/set_client_dh_params'
11
+ require_relative 'tl/auth_key/dh_gen_response'
5
12
 
6
13
  module MTProto
7
14
  class AuthKeyGenerator
8
- attr_reader :connection, :auth_key, :server_salt, :time_offset
15
+ attr_reader :connection, :auth_key, :server_salt, :time_offset, :timeout
16
+
17
+ def initialize(connection, public_key, dc_number = nil, test_mode: false, timeout: 10)
18
+ raise ArgumentError, "public_key is required" if public_key.nil? || public_key.empty?
19
+ raise ArgumentError, "dc_number must be positive. Use test_mode: true for test DCs" if dc_number && dc_number < 0
9
20
 
10
- def initialize(connection)
11
21
  @connection = connection
22
+ @public_key = public_key
23
+ @dc_number = dc_number
24
+ @test_mode = test_mode
25
+ @timeout = timeout
12
26
  @auth_key = nil
13
27
  @server_salt = nil
14
28
  @time_offset = 0
@@ -73,12 +87,13 @@ module MTProto
73
87
 
74
88
  def req_pq_multi
75
89
  nonce = SecureRandom.random_bytes(16)
76
- message = TL::Message.req_pq_multi(nonce)
90
+ body = TL::AuthKey::ReqPqMulti.build(nonce)
91
+ message = TL::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
77
92
  @connection.send(message.serialize)
78
93
 
79
- response_data = @connection.recv(timeout: 10)
94
+ response_data = @connection.recv(timeout: @timeout)
80
95
  response_message = TL::Message.deserialize(response_data)
81
- res_pq = response_message.parse_res_pq
96
+ res_pq = TL::AuthKey::ResPq.parse(response_message.body)
82
97
 
83
98
  raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
84
99
 
@@ -86,13 +101,19 @@ module MTProto
86
101
  end
87
102
 
88
103
  def find_server_key(fingerprints)
89
- server_key = Crypto::RSAKey.find_by_fingerprint(fingerprints)
104
+ server_key = Crypto::RSAKey.find_by_fingerprint(fingerprints, @public_key)
90
105
  raise 'No matching RSA key found!' unless server_key
91
106
 
92
107
  server_key
93
108
  end
94
109
 
95
110
  def encrypt_pq_inner_data(res_pq, p, q, server_key, new_nonce)
111
+ raise ArgumentError, "dc_number is required for auth key generation" if @dc_number.nil?
112
+
113
+ # For test DCs, add 10000 to the DC number per MTProto spec
114
+ # For production DCs, use the DC number as-is
115
+ dc_value = @test_mode ? (@dc_number + 10000) : @dc_number
116
+
96
117
  inner_data = TL::PQInnerData.new(
97
118
  pq: Crypto::Factorization.bytes_to_integer(res_pq[:pq]),
98
119
  p: p,
@@ -100,14 +121,14 @@ module MTProto
100
121
  nonce: res_pq[:nonce],
101
122
  server_nonce: res_pq[:server_nonce],
102
123
  new_nonce: new_nonce,
103
- dc: @connection.port == 443 ? 2 : 1
124
+ dc: dc_value
104
125
  )
105
126
 
106
127
  Crypto::RSA_PAD.encrypt(inner_data.serialize, server_key)
107
128
  end
108
129
 
109
130
  def send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
110
- message = TL::Message.req_DH_params(
131
+ body = TL::AuthKey::ReqDHParams.build(
111
132
  nonce: res_pq[:nonce],
112
133
  server_nonce: res_pq[:server_nonce],
113
134
  p: p,
@@ -115,12 +136,13 @@ module MTProto
115
136
  public_key_fingerprint: server_key.fingerprint,
116
137
  encrypted_data: encrypted_data
117
138
  )
139
+ message = TL::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
118
140
 
119
141
  @connection.send(message.serialize)
120
142
 
121
- response_data = @connection.recv(timeout: 10)
143
+ response_data = @connection.recv(timeout: @timeout)
122
144
  response_message = TL::Message.deserialize(response_data)
123
- server_dh_params = response_message.parse_server_DH_params_ok
145
+ server_dh_params = TL::AuthKey::ServerDHParams.parse(response_message.body)
124
146
 
125
147
  raise 'Nonce mismatch!' unless server_dh_params[:nonce] == res_pq[:nonce]
126
148
  raise 'Server nonce mismatch!' unless server_dh_params[:server_nonce] == res_pq[:server_nonce]
@@ -178,17 +200,18 @@ module MTProto
178
200
  tmp_aes_iv
179
201
  )
180
202
 
181
- message = TL::Message.set_client_DH_params(
203
+ body = TL::AuthKey::SetClientDHParams.build(
182
204
  nonce: res_pq[:nonce],
183
205
  server_nonce: res_pq[:server_nonce],
184
206
  encrypted_data: client_dh_encrypted
185
207
  )
208
+ message = TL::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
186
209
 
187
210
  @connection.send(message.serialize)
188
211
 
189
- response_data = @connection.recv(timeout: 10)
212
+ response_data = @connection.recv(timeout: @timeout)
190
213
  response_message = TL::Message.deserialize(response_data)
191
- response_message.parse_dh_gen_response
214
+ TL::AuthKey::DHGenResponse.parse(response_message.body)
192
215
  end
193
216
 
194
217
  def verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../message_id'
4
+
5
+ module MTProto
6
+ class Client
7
+ class RPC
8
+ CONSTRUCTOR_RPC_RESULT = 0xf35c6d01
9
+ CONSTRUCTOR_MSGS_ACK = 0x62d6b459
10
+ CONSTRUCTOR_BAD_SERVER_SALT = 0xedab447b
11
+
12
+ def initialize(client)
13
+ @client = client
14
+ @connection_initialized = false
15
+ end
16
+
17
+ def call(body, content_related: true)
18
+ raise 'Auth key not generated' unless @client.auth_key
19
+ raise 'Session not initialized' unless @client.session
20
+
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)
34
+
35
+ response_data = @client.connection.recv(timeout: @client.timeout)
36
+
37
+ decrypted = EncryptedMessage.decrypt(
38
+ auth_key: @client.auth_key,
39
+ encrypted_message_data: response_data,
40
+ sender: :server
41
+ )
42
+
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
63
+
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
77
+
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]
127
+ end
128
+
129
+ return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
130
+
131
+ response_body
132
+ end
133
+
134
+ private
135
+
136
+ def update_server_salt(server_salt)
137
+ @client.instance_variable_set(:@server_salt, server_salt)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -2,64 +2,62 @@
2
2
 
3
3
  require 'securerandom'
4
4
  require 'digest'
5
+ require 'base64'
5
6
  require_relative 'transport/tcp_connection'
6
7
  require_relative 'transport/abridged_packet_codec'
7
8
  require_relative 'tl/message'
9
+ require_relative 'client/rpc'
10
+ require_relative 'tl/method_builder'
8
11
 
9
12
  module MTProto
10
13
  class Client
11
- DC_ADDRESSES = {
12
- 1 => ['149.154.175.50', 443],
13
- 2 => ['149.154.167.51', 443],
14
- 3 => ['149.154.175.100', 443],
15
- 4 => ['149.154.167.91', 443],
16
- 5 => ['91.108.56.130', 443]
17
- }.freeze
14
+ include TL::MethodBuilder
18
15
 
19
- attr_reader :connection, :server_key, :auth_key, :server_salt, :time_offset, :session
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
20
18
 
21
- def initialize(dc_id: 2)
22
- host, port = DC_ADDRESSES[dc_id]
23
- raise ArgumentError, "Unknown DC ID: #{dc_id}" unless host
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
24
22
 
25
23
  codec = Transport::AbridgedPacketCodec.new
26
24
  @connection = Transport::TCPConnection.new(host, port, codec)
25
+ @public_key = public_key
26
+ @dc_number = dc_number
27
+ @test_mode = test_mode
28
+ @timeout = timeout
27
29
  @server_key = nil
28
30
  @auth_key = nil
29
31
  @server_salt = nil
30
32
  @time_offset = 0
31
33
  @session = nil
32
34
  @connection_initialized = false
35
+ @user_id = nil
36
+ @access_hash = nil
37
+
38
+ # Client configuration defaults
39
+ @api_id = api_id || 0
40
+ @api_hash = api_hash || ''
41
+ @device_model = 'Ruby MTProto'
42
+ @system_version = RUBY_DESCRIPTION
43
+ @app_version = '0.1.0'
44
+ @system_lang_code = 'en'
45
+ @lang_pack = ''
46
+ @lang_code = 'en'
33
47
  end
34
48
 
35
- def connect
36
- @connection.connect
49
+ def connect!
50
+ @connection.connect!
37
51
  end
38
52
 
39
- def disconnect
40
- @connection.close
53
+ def disconnect!
54
+ @connection.close if @connection
41
55
  end
42
56
 
43
- def req_pq_multi
44
- nonce = SecureRandom.random_bytes(16)
57
+ def exchange_keys!
58
+ raise ArgumentError, "public_key is required for auth key generation" if @public_key.nil? || @public_key.empty?
45
59
 
46
- message = TL::Message.req_pq_multi(nonce)
47
- payload = message.serialize
48
-
49
- @connection.send(payload)
50
-
51
- response_data = @connection.recv(timeout: 10)
52
- response_message = TL::Message.deserialize(response_data)
53
-
54
- res_pq = response_message.parse_res_pq
55
-
56
- raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
57
-
58
- res_pq
59
- end
60
-
61
- def make_auth_key
62
- generator = AuthKeyGenerator.new(@connection)
60
+ generator = AuthKeyGenerator.new(@connection, @public_key, @dc_number, test_mode: @test_mode, timeout: @timeout)
63
61
  result = generator.generate
64
62
 
65
63
  @auth_key = generator.auth_key
@@ -70,166 +68,43 @@ module MTProto
70
68
  result
71
69
  end
72
70
 
73
- def generate_msg_id
74
- time = Time.now.to_f + @time_offset
75
- msg_id = (time * (2**32)).to_i
76
- (msg_id / 4) * 4
71
+ def rpc
72
+ @rpc ||= RPC.new(self)
77
73
  end
78
74
 
79
- def rpc_call(body, content_related: true)
80
- raise 'Auth key not generated' unless @auth_key
81
- raise 'Session not initialized' unless @session
82
-
83
- msg_id = generate_msg_id
84
- seq_no = @session.next_seq_no(content_related: content_related)
75
+ def save_session
76
+ raise 'Cannot save session: auth_key not set' unless @auth_key
85
77
 
86
- encrypted_msg = EncryptedMessage.encrypt(
87
- auth_key: @auth_key,
78
+ {
79
+ auth_key: Base64.strict_encode64(@auth_key),
88
80
  server_salt: @server_salt,
89
- session_id: @session.session_id,
90
- msg_id: msg_id,
91
- seq_no: seq_no,
92
- body: body
93
- )
94
-
95
- @connection.send(encrypted_msg.serialize)
96
-
97
- response_data = @connection.recv(timeout: 10)
98
-
99
- decrypted = EncryptedMessage.decrypt(
100
- auth_key: @auth_key,
101
- encrypted_message_data: response_data,
102
- sender: :server
103
- )
104
-
105
- response_body = decrypted[:body]
106
-
107
- constructor = response_body[0, 4].unpack1('L<')
108
- puts " [RPC] First response constructor: 0x#{constructor.to_s(16)} (#{response_body.bytesize} bytes)"
109
-
110
- if constructor == TL::NewSessionCreated::CONSTRUCTOR
111
- puts " [RPC] Got new_session_created, waiting for actual response..."
112
- session_info = TL::NewSessionCreated.deserialize(response_body)
113
- @server_salt = session_info.server_salt
114
- puts " [RPC] Updated server_salt to: 0x#{@server_salt.to_s(16)}"
115
-
116
- response_data = @connection.recv(timeout: 10)
117
- puts " [RPC] Second response received (#{response_data.bytesize} bytes encrypted)"
118
- decrypted = EncryptedMessage.decrypt(
119
- auth_key: @auth_key,
120
- encrypted_message_data: response_data,
121
- sender: :server
122
- )
123
- response_body = decrypted[:body]
124
- constructor = response_body[0, 4].unpack1('L<')
125
- puts " [RPC] Second response constructor: 0x#{constructor.to_s(16)} (#{response_body.bytesize} bytes)"
126
- end
127
-
128
- if constructor == TL::Message::CONSTRUCTOR_MSG_CONTAINER
129
- puts " [RPC] Response is a container, unpacking..."
130
- container = TL::MsgContainer.deserialize(response_body)
131
- puts " [RPC] Container has #{container.messages.size} messages"
132
-
133
- container.messages.each_with_index do |msg, i|
134
- msg_constructor = msg[:body][0, 4].unpack1('L<')
135
- puts " [RPC] Message #{i}: constructor=0x#{msg_constructor.to_s(16)}, size=#{msg[:body].bytesize}"
136
- end
137
-
138
- rpc_result = container.messages.find do |msg|
139
- msg[:body][0, 4].unpack1('L<') == 0xf35c6d01
140
- end
141
-
142
- if rpc_result
143
- puts " [RPC] Found rpc_result in container, extracting..."
144
- return rpc_result[:body][12..]
145
- end
146
-
147
- new_session = container.messages.find { |msg| msg[:body][0, 4].unpack1('L<') == TL::NewSessionCreated::CONSTRUCTOR }
148
- if new_session
149
- puts " [RPC] Container has new_session_created, updating salt and waiting for RPC result..."
150
- session_info = TL::NewSessionCreated.deserialize(new_session[:body])
151
- @server_salt = session_info.server_salt
152
- puts " [RPC] Updated server_salt to: 0x#{@server_salt.to_s(16)}"
153
-
154
- response_data = @connection.recv(timeout: 10)
155
- puts " [RPC] Next response received (#{response_data.bytesize} bytes encrypted)"
156
- decrypted = EncryptedMessage.decrypt(
157
- auth_key: @auth_key,
158
- encrypted_message_data: response_data,
159
- sender: :server
160
- )
161
- response_body = decrypted[:body]
162
- constructor = response_body[0, 4].unpack1('L<')
163
- puts " [RPC] Next response constructor: 0x#{constructor.to_s(16)} (#{response_body.bytesize} bytes)"
164
-
165
- if constructor == 0xf35c6d01
166
- puts " [RPC] Next response is rpc_result, extracting payload..."
167
- return response_body[12..]
168
- end
169
-
170
- if constructor == TL::Message::CONSTRUCTOR_MSG_CONTAINER
171
- puts " [RPC] Next response is also a container, looking for rpc_result..."
172
- container = TL::MsgContainer.deserialize(response_body)
173
- rpc_result = container.messages.find { |msg| msg[:body][0, 4].unpack1('L<') == 0xf35c6d01 }
174
- return rpc_result[:body][12..] if rpc_result
175
- end
176
-
177
- return response_body
178
- end
179
-
180
- puts " [RPC] No rpc_result found, returning first message body"
181
- return container.messages.first[:body]
182
- end
183
-
184
- if constructor == 0xf35c6d01
185
- puts " [RPC] Response is rpc_result, extracting payload..."
186
- return response_body[12..]
187
- end
188
-
189
- puts " [RPC] Returning raw response body (constructor: 0x#{constructor.to_s(16)})"
190
- response_body
81
+ user_id: @user_id,
82
+ access_hash: @access_hash,
83
+ dc_number: @dc_number,
84
+ time_offset: @time_offset
85
+ }
191
86
  end
192
87
 
193
- def invoke_with_layer(layer, query)
194
- body = TL::Serializer.serialize_int(0xda9b0d0d)
195
- body += TL::Serializer.serialize_int(layer)
196
- body += query
197
- body
198
- end
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]
92
+
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]
98
+
99
+ # Create a fresh session for the new connection
100
+ @session = Session.new
199
101
 
200
- def init_connection(api_id, device_model, system_version, app_version, system_lang_code, lang_code, query)
201
- body = TL::Serializer.serialize_int(0xc1cd5ea9)
202
- flags = 0
203
- body += TL::Serializer.serialize_int(flags)
204
- body += TL::Serializer.serialize_int(api_id)
205
- body += TL::Serializer.serialize_string(device_model)
206
- body += TL::Serializer.serialize_string(system_version)
207
- body += TL::Serializer.serialize_string(app_version)
208
- body += TL::Serializer.serialize_string(system_lang_code)
209
- body += TL::Serializer.serialize_string('')
210
- body += TL::Serializer.serialize_string(lang_code)
211
- body += query
212
- body
102
+ true
213
103
  end
214
104
 
215
- def help_get_config
216
- query = [0xc4f9186b].pack('L<')
217
-
218
- unless @connection_initialized
219
- query = init_connection(
220
- 123456,
221
- 'Ruby MTProto',
222
- 'Ruby 3.x',
223
- '0.1.0',
224
- 'en',
225
- 'en',
226
- query
227
- )
228
- query = invoke_with_layer(214, query)
229
- @connection_initialized = true
230
- end
231
-
232
- rpc_call(query)
105
+ def update_user(user_id:, access_hash: nil)
106
+ @user_id = user_id
107
+ @access_hash = access_hash
233
108
  end
234
109
  end
235
110
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'aes_ige/aes_ige'
4
+ require 'aes_ige'
5
5
  rescue LoadError => e
6
6
  warn "Failed to load AES-IGE C extension: #{e.message}"
7
7
  warn 'Run: cd ext/aes_ige && ruby extconf.rb && make'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'factorization/factorization'
4
+ require 'factorization'
5
5
  rescue LoadError => e
6
6
  warn "Failed to load Factorization C extension: #{e.message}"
7
7
  warn 'Run: cd ext/factorization && ruby extconf.rb && make'
@@ -6,30 +6,24 @@ require 'digest'
6
6
  module MTProto
7
7
  module Crypto
8
8
  class RSAKey
9
- TELEGRAM_KEY = <<~PEM
10
- -----BEGIN RSA PUBLIC KEY-----
11
- MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g
12
- 5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO
13
- 62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/
14
- +aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9
15
- t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs
16
- 5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB
17
- -----END RSA PUBLIC KEY-----
18
- PEM
19
-
20
9
  attr_reader :key, :fingerprint
21
10
 
22
11
  def initialize(pem_string)
12
+ raise ArgumentError, "pem_string is required" if pem_string.nil? || pem_string.empty?
13
+
23
14
  @key = OpenSSL::PKey::RSA.new(pem_string)
24
15
  @fingerprint = calculate_fingerprint
25
16
  end
26
17
 
27
- def self.telegram_key
28
- @telegram_key ||= new(TELEGRAM_KEY)
18
+ def self.from_pem(pem_string)
19
+ new(pem_string)
29
20
  end
30
21
 
31
- def self.find_by_fingerprint(fingerprints)
32
- telegram_key if fingerprints.include?(telegram_key.fingerprint)
22
+ def self.find_by_fingerprint(fingerprints, public_key)
23
+ raise ArgumentError, "public_key is required" if public_key.nil? || public_key.empty?
24
+
25
+ key = new(public_key)
26
+ key if fingerprints.include?(key.fingerprint)
33
27
  end
34
28
 
35
29
  private