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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93bf74e47e4eb9cea99def85727f559cc3fcbe484411ba181b6ac596ed24fcbe
4
- data.tar.gz: c0b1bc2e59a38eb2042d938692ba1959bbad1633a0594858ce9c1991444f4ef0
3
+ metadata.gz: 46c1ad0b399a4ecb8502c4783d7a032be25a502620357a0a04009d01ae3feb48
4
+ data.tar.gz: 28f124987f99bb50e8fa4cc6a9f00b77a926b7285ab159039987369ab1359a88
5
5
  SHA512:
6
- metadata.gz: 9a5cd8f10a36c676db3ba4e642f9f7d1a209600278b3d571c86dfcf78f489613603919a8c1f950b8e3b71e2a1a721a4b0fab721b2e318eb30ff7960edfe6b4df
7
- data.tar.gz: e98baafabd0e49ea34d1cc7d92b6761a1b899fec53c19faccd96ada01989b8784ac6114e597f28a1f0a15f34ed95cb600948f89decdee66c0d84c6ec1e716194
6
+ metadata.gz: 89d8dbe1f183814f2a6f9b485d85f0ae6b92c4895aae6c2493a41a54a740090af30910c0086de0e82d53415905e32a7a2a8b0273eff66393c594ae5ebe1bee1b
7
+ data.tar.gz: 31793f4d495ccceb6a18959261673e8d3c0388ff9bd02f8bf848ea093dcedde7457412763aeaa87f942251c49906dcd7b39bb9d03b3ade3f64d4b17a8df433f5
@@ -6,18 +6,12 @@ require 'mkmf'
6
6
  dir_config('openssl')
7
7
 
8
8
  # Check for OpenSSL headers and libraries
9
- unless have_header('openssl/aes.h')
10
- abort 'OpenSSL headers not found. Please install OpenSSL development files.'
11
- end
9
+ abort 'OpenSSL headers not found. Please install OpenSSL development files.' unless have_header('openssl/aes.h')
12
10
 
13
- unless have_library('ssl') && have_library('crypto')
14
- abort 'OpenSSL libraries not found. Please install OpenSSL.'
15
- end
11
+ abort 'OpenSSL libraries not found. Please install OpenSSL.' unless have_library('ssl') && have_library('crypto')
16
12
 
17
13
  # Check for AES_ige_encrypt function
18
- unless have_func('AES_ige_encrypt', 'openssl/aes.h')
19
- abort 'AES_ige_encrypt function not found in OpenSSL library.'
20
- end
14
+ abort 'AES_ige_encrypt function not found in OpenSSL library.' unless have_func('AES_ige_encrypt', 'openssl/aes.h')
21
15
 
22
16
  # Suppress deprecation warnings for OpenSSL 3.0
23
17
  $CFLAGS += ' -Wno-deprecated-declarations'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mkmf'
2
4
 
3
5
  create_makefile('factorization/factorization')
@@ -2,21 +2,23 @@
2
2
 
3
3
  require 'securerandom'
4
4
  require 'digest'
5
- require_relative 'message_id'
6
- require_relative 'type/auth_key/req_pq_multi'
7
- require_relative 'type/auth_key/res_pq'
8
- require_relative 'type/auth_key/req_dh_params'
9
- require_relative 'type/auth_key/server_dh_params'
10
- require_relative 'type/auth_key/set_client_dh_params'
11
- require_relative 'type/auth_key/dh_gen_response'
5
+ require_relative 'tl/objects/req_pq_multi'
6
+ require_relative 'tl/objects/res_pq'
7
+ require_relative 'tl/objects/pq_inner_data'
8
+ require_relative 'tl/objects/req_dh_params'
9
+ require_relative 'tl/objects/server_dh_params'
10
+ require_relative 'tl/objects/server_dh_inner_data'
11
+ require_relative 'tl/objects/client_dh_inner_data'
12
+ require_relative 'tl/objects/set_client_dh_params'
13
+ require_relative 'tl/objects/dh_gen_response'
12
14
 
13
15
  module MTProto
14
16
  class AuthKeyGenerator
15
17
  attr_reader :connection, :auth_key, :server_salt, :time_offset, :timeout
16
18
 
17
19
  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
20
+ raise ArgumentError, 'public_key is required' if public_key.nil? || public_key.empty?
21
+ raise ArgumentError, 'dc_number must be positive. Use test_mode: true for test DCs' if dc_number && dc_number < 0
20
22
 
21
23
  @connection = connection
22
24
  @public_key = public_key
@@ -29,13 +31,15 @@ module MTProto
29
31
  end
30
32
 
31
33
  def generate
32
- res_pq = req_pq_multi
33
- server_key = find_server_key(res_pq[:fingerprints])
34
- p, q = Crypto::Factorization.factorize_pq(res_pq[:pq])
34
+ res_pq = send_and_receive(TL::ReqPqMulti.new(SecureRandom.random_bytes(16)), TL::ResPq)
35
+
36
+ server_key = Crypto::RSAKey.find_by_fingerprint(res_pq.fingerprints, @public_key)
37
+ raise 'No matching RSA key found!' unless server_key
38
+
39
+ p, q = Crypto::Factorization.factorize_pq(res_pq.pq)
35
40
  new_nonce = SecureRandom.random_bytes(32)
36
41
 
37
- encrypted_data = encrypt_pq_inner_data(res_pq, p, q, server_key, new_nonce)
38
- server_dh_params = send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
42
+ server_dh_params = send_req_dh_params(res_pq, p, q, server_key, new_nonce)
39
43
  server_dh_inner_data = decrypt_server_dh_params(res_pq, new_nonce, server_dh_params)
40
44
 
41
45
  Crypto::DHValidator.validate_dh_params(
@@ -55,8 +59,8 @@ module MTProto
55
59
  server_dh_inner_data.dh_prime
56
60
  )
57
61
 
58
- tmp_aes_key = Crypto::AuthKeyHelper.derive_tmp_aes_key(new_nonce, res_pq[:server_nonce])
59
- tmp_aes_iv = Crypto::AuthKeyHelper.derive_tmp_aes_iv(new_nonce, res_pq[:server_nonce])
62
+ tmp_aes_key = Crypto::AuthKeyHelper.derive_tmp_aes_key(new_nonce, res_pq.server_nonce)
63
+ tmp_aes_iv = Crypto::AuthKeyHelper.derive_tmp_aes_iv(new_nonce, res_pq.server_nonce)
60
64
 
61
65
  dh_gen_response = send_client_dh_params(
62
66
  res_pq,
@@ -66,96 +70,69 @@ module MTProto
66
70
  tmp_aes_iv
67
71
  )
68
72
 
69
- verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
73
+ verify_dh_gen_response(dh_gen_response, new_nonce, auth_key)
70
74
 
71
75
  @auth_key = auth_key
72
- @server_salt = compute_server_salt(new_nonce, res_pq[:server_nonce])
76
+ @server_salt = compute_server_salt(new_nonce, res_pq.server_nonce)
73
77
  @time_offset = server_dh_inner_data.server_time - Time.now.to_i
74
78
 
75
79
  {
76
80
  auth_key: @auth_key,
77
81
  server_salt: @server_salt,
78
- time_offset: @time_offset,
79
- server_dh_params: server_dh_params,
80
- server_dh_inner_data: server_dh_inner_data,
81
- client_dh_params: client_dh_params,
82
- dh_gen_response: dh_gen_response
82
+ time_offset: @time_offset
83
83
  }
84
84
  end
85
85
 
86
86
  private
87
87
 
88
- def req_pq_multi
89
- nonce = SecureRandom.random_bytes(16)
90
- body = Type::AuthKey::ReqPqMulti.build(nonce)
91
- message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
92
- @connection.send(message.serialize)
88
+ def send_and_receive(rpc, response_class)
89
+ message = Message.new(rpc.body)
90
+ @connection.send(Transport::Packet.new(message.bytes))
93
91
 
94
- response_data = @connection.recv(timeout: @timeout)
95
- response_message = Type::Message.deserialize(response_data)
96
- res_pq = Type::AuthKey::ResPq.parse(response_message.body)
92
+ response_packet = @connection.receive
93
+ raise 'Receive timeout' if response_packet.nil?
97
94
 
98
- raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
99
-
100
- res_pq
101
- end
95
+ result = response_class.parse(Message.parse(response_packet))
102
96
 
103
- def find_server_key(fingerprints)
104
- server_key = Crypto::RSAKey.find_by_fingerprint(fingerprints, @public_key)
105
- raise 'No matching RSA key found!' unless server_key
97
+ raise 'Nonce mismatch!' if rpc.respond_to?(:nonce) && result.nonce != rpc.nonce
98
+ raise 'Server nonce mismatch!' if rpc.respond_to?(:server_nonce) && result.server_nonce != rpc.server_nonce
106
99
 
107
- server_key
100
+ result
108
101
  end
109
102
 
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?
103
+ def send_req_dh_params(res_pq, p, q, server_key, new_nonce)
104
+ raise ArgumentError, 'dc_number is required for auth key generation' if @dc_number.nil?
112
105
 
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
106
+ dc_value = @test_mode ? (@dc_number + 10_000) : @dc_number
116
107
 
117
- inner_data = Type::PQInnerData.new(
118
- pq: Crypto::Factorization.bytes_to_integer(res_pq[:pq]),
108
+ inner_data = TL::PQInnerData.new(
109
+ pq: Crypto::Factorization.bytes_to_integer(res_pq.pq),
119
110
  p: p,
120
111
  q: q,
121
- nonce: res_pq[:nonce],
122
- server_nonce: res_pq[:server_nonce],
112
+ nonce: res_pq.nonce,
113
+ server_nonce: res_pq.server_nonce,
123
114
  new_nonce: new_nonce,
124
115
  dc: dc_value
125
116
  )
117
+ encrypted_data = Crypto::RSA_PAD.encrypt(inner_data.body.pack('C*'), server_key)
126
118
 
127
- Crypto::RSA_PAD.encrypt(inner_data.serialize, server_key)
128
- end
129
-
130
- def send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
131
- body = Type::AuthKey::ReqDHParams.build(
132
- nonce: res_pq[:nonce],
133
- server_nonce: res_pq[:server_nonce],
119
+ rpc = TL::ReqDHParams.new(
120
+ nonce: res_pq.nonce,
121
+ server_nonce: res_pq.server_nonce,
134
122
  p: p,
135
123
  q: q,
136
124
  public_key_fingerprint: server_key.fingerprint,
137
125
  encrypted_data: encrypted_data
138
126
  )
139
- message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
140
-
141
- @connection.send(message.serialize)
142
-
143
- response_data = @connection.recv(timeout: @timeout)
144
- response_message = Type::Message.deserialize(response_data)
145
- server_dh_params = Type::AuthKey::ServerDHParams.parse(response_message.body)
146
-
147
- raise 'Nonce mismatch!' unless server_dh_params[:nonce] == res_pq[:nonce]
148
- raise 'Server nonce mismatch!' unless server_dh_params[:server_nonce] == res_pq[:server_nonce]
149
-
150
- server_dh_params
127
+ send_and_receive(rpc, TL::ServerDHParams)
151
128
  end
152
129
 
153
130
  def decrypt_server_dh_params(res_pq, new_nonce, server_dh_params)
154
- tmp_aes_key = Crypto::AuthKeyHelper.derive_tmp_aes_key(new_nonce, res_pq[:server_nonce])
155
- tmp_aes_iv = Crypto::AuthKeyHelper.derive_tmp_aes_iv(new_nonce, res_pq[:server_nonce])
131
+ tmp_aes_key = Crypto::AuthKeyHelper.derive_tmp_aes_key(new_nonce, res_pq.server_nonce)
132
+ tmp_aes_iv = Crypto::AuthKeyHelper.derive_tmp_aes_iv(new_nonce, res_pq.server_nonce)
156
133
 
157
134
  answer_with_hash = Crypto::AES_IGE.decrypt_ige(
158
- server_dh_params[:encrypted_answer],
135
+ server_dh_params.encrypted_answer,
159
136
  tmp_aes_key,
160
137
  tmp_aes_iv
161
138
  )
@@ -172,23 +149,18 @@ module MTProto
172
149
  answer = answer[0..-2]
173
150
  end
174
151
 
175
- server_dh_inner_data = Type::ServerDHInnerData.deserialize(answer)
176
-
177
- raise 'Nonce mismatch in DH inner data!' unless server_dh_inner_data.nonce == res_pq[:nonce]
178
- raise 'Server nonce mismatch in DH inner data!' unless server_dh_inner_data.server_nonce == res_pq[:server_nonce]
179
-
180
- server_dh_inner_data
152
+ TL::ServerDHInnerData.parse(answer)
181
153
  end
182
154
 
183
- def send_client_dh_params(res_pq, new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
184
- client_dh_inner_data = Type::ClientDHInnerData.new(
185
- nonce: res_pq[:nonce],
186
- server_nonce: res_pq[:server_nonce],
155
+ def send_client_dh_params(res_pq, _new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
156
+ client_dh_inner_data = TL::ClientDHInnerData.new(
157
+ nonce: res_pq.nonce,
158
+ server_nonce: res_pq.server_nonce,
187
159
  retry_id: 0,
188
160
  g_b: client_dh_params[:g_b_bytes]
189
161
  )
190
162
 
191
- client_dh_data = client_dh_inner_data.serialize
163
+ client_dh_data = client_dh_inner_data.body.pack('C*')
192
164
  client_dh_data_with_hash = Digest::SHA1.digest(client_dh_data) + client_dh_data
193
165
 
194
166
  padding_length = (16 - (client_dh_data_with_hash.bytesize % 16)) % 16
@@ -200,39 +172,30 @@ module MTProto
200
172
  tmp_aes_iv
201
173
  )
202
174
 
203
- body = Type::AuthKey::SetClientDHParams.build(
204
- nonce: res_pq[:nonce],
205
- server_nonce: res_pq[:server_nonce],
175
+ rpc = TL::SetClientDHParams.new(
176
+ nonce: res_pq.nonce,
177
+ server_nonce: res_pq.server_nonce,
206
178
  encrypted_data: client_dh_encrypted
207
179
  )
208
- message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
209
-
210
- @connection.send(message.serialize)
211
-
212
- response_data = @connection.recv(timeout: @timeout)
213
- response_message = Type::Message.deserialize(response_data)
214
- Type::AuthKey::DHGenResponse.parse(response_message.body)
180
+ send_and_receive(rpc, TL::DHGenResponse)
215
181
  end
216
182
 
217
- def verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
218
- raise 'Nonce mismatch in DH response!' unless dh_gen_response[:nonce] == res_pq[:nonce]
219
- raise 'Server nonce mismatch in DH response!' unless dh_gen_response[:server_nonce] == res_pq[:server_nonce]
220
-
183
+ def verify_dh_gen_response(dh_gen_response, new_nonce, auth_key)
221
184
  auth_key_hash = Digest::SHA1.digest(auth_key)
222
185
  auth_key_aux_hash = auth_key_hash[0, 8]
223
186
 
224
- expected_new_nonce_hash = case dh_gen_response[:status]
225
- when :ok
226
- Digest::SHA1.digest(new_nonce + "\x01".b + auth_key_aux_hash)[-16..]
227
- when :retry
228
- Digest::SHA1.digest(new_nonce + "\x02".b + auth_key_aux_hash)[-16..]
229
- when :fail
230
- Digest::SHA1.digest(new_nonce + "\x03".b + auth_key_aux_hash)[-16..]
231
- end
187
+ expected_new_nonce_hash = case dh_gen_response.status
188
+ when :ok
189
+ Digest::SHA1.digest(new_nonce + "\x01".b + auth_key_aux_hash)[-16..]
190
+ when :retry
191
+ Digest::SHA1.digest(new_nonce + "\x02".b + auth_key_aux_hash)[-16..]
192
+ when :fail
193
+ Digest::SHA1.digest(new_nonce + "\x03".b + auth_key_aux_hash)[-16..]
194
+ end
232
195
 
233
- raise 'new_nonce_hash mismatch!' unless dh_gen_response[:new_nonce_hash] == expected_new_nonce_hash
196
+ raise 'new_nonce_hash mismatch!' unless dh_gen_response.new_nonce_hash == expected_new_nonce_hash
234
197
 
235
- case dh_gen_response[:status]
198
+ case dh_gen_response.status
236
199
  when :retry
237
200
  raise 'Server requested retry - not implemented'
238
201
  when :fail
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Binary
5
+ def b_u32(bytes)
6
+ bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)
7
+ end
8
+
9
+ def b_u64(bytes)
10
+ b_u32(bytes[0, 4]) | (b_u32(bytes[4, 4]) << 32)
11
+ end
12
+
13
+ def u32_b(value)
14
+ [value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff]
15
+ end
16
+
17
+ def u64_b(value)
18
+ u32_b(value & 0xffffffff) + u32_b(value >> 32)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/get_password'
4
+ require_relative '../../tl/objects/check_password'
5
+ require_relative '../../tl/objects/account_password'
6
+ require_relative '../../tl/objects/authorization'
7
+ require_relative '../../crypto/srp'
8
+
9
+ module MTProto
10
+ class Client
11
+ class API
12
+ def check_password(password:)
13
+ pwd = rpc_call(TL::GetPassword.new, TL::AccountPassword).body
14
+
15
+ raise 'Account does not have 2FA enabled' unless pwd.has_password
16
+
17
+ srp_result = Crypto::SRP.compute_check(
18
+ algo: pwd.current_algo,
19
+ srp_b: pwd.srp_b,
20
+ srp_id: pwd.srp_id,
21
+ password: password
22
+ )
23
+
24
+ result = rpc_call(
25
+ TL::CheckPassword.new(
26
+ srp_id: srp_result[:srp_id],
27
+ a: srp_result[:a],
28
+ m1: srp_result[:m1]
29
+ ),
30
+ TL::Authorization
31
+ ).body
32
+
33
+ if result.authorization? && result.user_id
34
+ @client.update_user(user_id: result.user_id, access_hash: result.access_hash)
35
+ end
36
+
37
+ result
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/export_login_token'
4
+ require_relative '../../tl/objects/login_token'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def export_login_token(except_ids: [])
10
+ result = rpc_call(
11
+ TL::ExportLoginToken.new(
12
+ api_id: @client.api_id,
13
+ api_hash: @client.api_hash,
14
+ except_ids: except_ids
15
+ ),
16
+ TL::LoginToken
17
+ ).body
18
+
19
+ if result.success? && result.user_id
20
+ @client.update_user(user_id: result.user_id, access_hash: result.access_hash)
21
+ end
22
+
23
+ result
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/get_difference'
4
+ require_relative '../../tl/objects/updates_difference'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def get_updates_difference(pts:, date:, qts:, pts_limit: nil, qts_limit: nil, pts_total_limit: nil)
10
+ rpc_call(
11
+ TL::GetDifference.new(
12
+ pts: pts, date: date, qts: qts,
13
+ pts_limit: pts_limit, qts_limit: qts_limit,
14
+ pts_total_limit: pts_total_limit
15
+ ),
16
+ TL::UpdatesDifference
17
+ ).body
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/get_state'
4
+ require_relative '../../tl/objects/updates_state'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def get_updates_state
10
+ rpc_call(TL::GetState.new, TL::UpdatesState).body
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/get_users'
4
+ require_relative '../../tl/objects/users'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def get_users
10
+ rpc_call(TL::GetUsers.new, TL::Users).body
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/import_login_token'
4
+ require_relative '../../tl/objects/login_token'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def import_login_token(token:)
10
+ result = rpc_call(
11
+ TL::ImportLoginToken.new(token: token),
12
+ TL::LoginToken
13
+ ).body
14
+
15
+ if result.success? && result.user_id
16
+ @client.update_user(user_id: result.user_id, access_hash: result.access_hash)
17
+ end
18
+
19
+ result
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/send_code'
4
+ require_relative '../../tl/objects/sent_code'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def send_code(phone_number)
10
+ rpc_call(
11
+ TL::SendCode.new(
12
+ phone_number: phone_number,
13
+ api_id: @client.api_id,
14
+ api_hash: @client.api_hash
15
+ ),
16
+ TL::SentCode
17
+ ).body
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/sign_in'
4
+ require_relative '../../tl/objects/authorization'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def sign_in(phone_number:, phone_code_hash:, phone_code:)
10
+ result = rpc_call(
11
+ TL::SignIn.new(
12
+ phone_number: phone_number,
13
+ phone_code_hash: phone_code_hash,
14
+ phone_code: phone_code
15
+ ),
16
+ TL::Authorization
17
+ ).body
18
+
19
+ if result.authorization? && result.user_id
20
+ @client.update_user(user_id: result.user_id, access_hash: result.access_hash)
21
+ end
22
+
23
+ result
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'api/send_code'
4
+ require_relative 'api/sign_in'
5
+ require_relative 'api/export_login_token'
6
+ require_relative 'api/import_login_token'
7
+ require_relative 'api/check_password'
8
+ require_relative 'api/get_users'
9
+ require_relative 'api/get_updates_state'
10
+ require_relative 'api/get_updates_difference'
11
+
12
+ module MTProto
13
+ class Client
14
+ class API
15
+ def initialize(client)
16
+ @client = client
17
+ end
18
+
19
+ private
20
+
21
+ def rpc_call(request, response_class)
22
+ response = @client.rpc.call(request, response_class)
23
+ response.wait!(@client.timeout)
24
+ response
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'async/condition'
4
+
5
+ module MTProto
6
+ class Client
7
+ class RPC
8
+ class Response
9
+ attr_reader :msg_id, :body_bytes, :response_class
10
+
11
+ def initialize(msg_id, response_class, body_bytes)
12
+ @msg_id = msg_id
13
+ @response_class = response_class
14
+ @body_bytes = body_bytes
15
+ @condition = ::Async::Condition.new
16
+ @result = nil
17
+ @error = nil
18
+ end
19
+
20
+ def received?
21
+ !@result.nil? || !@error.nil?
22
+ end
23
+
24
+ def wait!(timeout = nil)
25
+ unless received?
26
+ if timeout
27
+ ::Async::Task.current.with_timeout(timeout) { receive_signal }
28
+ else
29
+ receive_signal
30
+ end
31
+ end
32
+
33
+ raise @error if @error
34
+
35
+ @result
36
+ end
37
+
38
+ def body
39
+ raise 'Response not received yet' unless received?
40
+ raise @error if @error
41
+
42
+ @result
43
+ end
44
+
45
+ def signal(raw_body)
46
+ @result = @response_class.parse(raw_body)
47
+ @condition.signal
48
+ end
49
+
50
+ def signal_error(error)
51
+ @error = error
52
+ @condition.signal
53
+ end
54
+
55
+ private
56
+
57
+ def receive_signal
58
+ @condition.wait
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end