mtproto 0.0.6 → 0.0.8
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.
- checksums.yaml +4 -4
- data/.env.example +4 -0
- data/lib/mtproto/async/middleware/base.rb +17 -0
- data/lib/mtproto/async/middleware/flood_wait.rb +42 -0
- data/lib/mtproto/async/request.rb +18 -0
- data/lib/mtproto/async/request_queue.rb +63 -0
- data/lib/mtproto/async_client.rb +201 -0
- data/lib/mtproto/auth_key_generator.rb +22 -12
- data/lib/mtproto/client/rpc.rb +165 -0
- data/lib/mtproto/client.rb +65 -176
- data/lib/mtproto/crypto/aes_ige.rb +1 -1
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/message_id.rb +13 -0
- data/lib/mtproto/rpc/get_config.rb +34 -0
- data/lib/mtproto/rpc/get_contacts.rb +29 -0
- data/lib/mtproto/rpc/get_updates_difference.rb +51 -0
- data/lib/mtproto/rpc/get_updates_state.rb +29 -0
- data/lib/mtproto/rpc/get_users.rb +29 -0
- data/lib/mtproto/rpc/ping.rb +33 -0
- data/lib/mtproto/rpc/send_code.rb +41 -0
- data/lib/mtproto/rpc/send_message.rb +47 -0
- data/lib/mtproto/rpc/sign_in.rb +48 -0
- data/lib/mtproto/type/auth_key/dh_gen_response.rb +37 -0
- data/lib/mtproto/type/auth_key/req_dh_params.rb +31 -0
- data/lib/mtproto/type/auth_key/req_pq_multi.rb +18 -0
- data/lib/mtproto/type/auth_key/res_pq.rb +62 -0
- data/lib/mtproto/type/auth_key/server_dh_params.rb +43 -0
- data/lib/mtproto/type/auth_key/set_client_dh_params.rb +25 -0
- data/lib/mtproto/{tl → type}/bad_msg_notification.rb +1 -1
- data/lib/mtproto/{tl → type}/client_dh_inner_data.rb +1 -1
- data/lib/mtproto/{tl → type}/code_settings.rb +1 -1
- data/lib/mtproto/{tl → type}/config.rb +1 -1
- data/lib/mtproto/{tl → type}/gzip_packed.rb +1 -1
- data/lib/mtproto/type/message.rb +38 -0
- data/lib/mtproto/{tl → type}/msg_container.rb +1 -1
- data/lib/mtproto/{tl → type}/new_session_created.rb +1 -1
- data/lib/mtproto/{tl/p_q_inner_data.rb → type/pq_inner_data.rb} +1 -1
- data/lib/mtproto/type/rpc/auth/authorization.rb +107 -0
- data/lib/mtproto/type/rpc/auth/send_code.rb +28 -0
- data/lib/mtproto/type/rpc/auth/sent_code.rb +36 -0
- data/lib/mtproto/type/rpc/auth/sign_in.rb +32 -0
- data/lib/mtproto/type/rpc/contacts/contacts.rb +155 -0
- data/lib/mtproto/type/rpc/contacts/get_contacts.rb +18 -0
- data/lib/mtproto/type/rpc/help/config.rb +35 -0
- data/lib/mtproto/type/rpc/help/get_config.rb +17 -0
- data/lib/mtproto/type/rpc/init_connection.rb +28 -0
- data/lib/mtproto/type/rpc/invoke_with_layer.rb +19 -0
- data/lib/mtproto/type/rpc/messages/send_message.rb +43 -0
- data/lib/mtproto/type/rpc/messages/updates.rb +87 -0
- data/lib/mtproto/type/rpc/ping.rb +18 -0
- data/lib/mtproto/type/rpc/pong.rb +46 -0
- data/lib/mtproto/type/rpc/updates/difference.rb +332 -0
- data/lib/mtproto/type/rpc/updates/get_difference.rb +42 -0
- data/lib/mtproto/type/rpc/updates/get_state.rb +17 -0
- data/lib/mtproto/type/rpc/updates/state.rb +59 -0
- data/lib/mtproto/type/rpc/users/get_users.rb +25 -0
- data/lib/mtproto/type/rpc/users/users.rb +99 -0
- data/lib/mtproto/{tl → type}/rpc_error.rb +1 -1
- data/lib/mtproto/{tl → type}/sent_code.rb +1 -1
- data/lib/mtproto/{tl → type}/serializer.rb +1 -1
- data/lib/mtproto/{tl → type}/server_dh_inner_data.rb +1 -1
- data/lib/mtproto/updates_poller.rb +111 -0
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +28 -14
- metadata +86 -18
- data/ext/aes_ige/Makefile +0 -273
- data/ext/factorization/Makefile +0 -273
- data/lib/mtproto/connection.rb +0 -103
- data/lib/mtproto/tl/message.rb +0 -252
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93bf74e47e4eb9cea99def85727f559cc3fcbe484411ba181b6ac596ed24fcbe
|
|
4
|
+
data.tar.gz: c0b1bc2e59a38eb2042d938692ba1959bbad1633a0594858ce9c1991444f4ef0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a5cd8f10a36c676db3ba4e642f9f7d1a209600278b3d571c86dfcf78f489613603919a8c1f950b8e3b71e2a1a721a4b0fab721b2e318eb30ff7960edfe6b4df
|
|
7
|
+
data.tar.gz: e98baafabd0e49ea34d1cc7d92b6761a1b899fec53c19faccd96ada01989b8784ac6114e597f28a1f0a15f34ed95cb600948f89decdee66c0d84c6ec1e716194
|
data/.env.example
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Async
|
|
5
|
+
module Middleware
|
|
6
|
+
class Base
|
|
7
|
+
def initialize(app)
|
|
8
|
+
@app = app
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(request, future, retry_fn, retries)
|
|
12
|
+
@app.call(request, future, retry_fn, retries)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module Async
|
|
7
|
+
module Middleware
|
|
8
|
+
class FloodWait < Base
|
|
9
|
+
def initialize(app, max_retries: 5)
|
|
10
|
+
super(app)
|
|
11
|
+
@max_retries = max_retries
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(request, future, retry_fn, retries)
|
|
15
|
+
wrapped = @app.call(request, future, retry_fn, retries)
|
|
16
|
+
|
|
17
|
+
wrapped.rescue do |error|
|
|
18
|
+
if flood_wait_error?(error) && retries < @max_retries
|
|
19
|
+
delay = extract_delay(error.error_message)
|
|
20
|
+
sleep(delay)
|
|
21
|
+
retry_fn.call.value!
|
|
22
|
+
else
|
|
23
|
+
raise error
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def flood_wait_error?(error)
|
|
31
|
+
error.is_a?(MTProto::RpcError) &&
|
|
32
|
+
error.error_code == 420 &&
|
|
33
|
+
error.error_message.start_with?('FLOOD_WAIT_')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def extract_delay(error_message)
|
|
37
|
+
error_message[11..-1].to_i
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Async
|
|
5
|
+
class Request
|
|
6
|
+
attr_reader :body, :metadata
|
|
7
|
+
|
|
8
|
+
def initialize(body:, metadata: {})
|
|
9
|
+
@body = body
|
|
10
|
+
@metadata = metadata
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def key
|
|
14
|
+
metadata[:key] || :default
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent-ruby'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module Async
|
|
7
|
+
class RequestQueue
|
|
8
|
+
def initialize
|
|
9
|
+
@queues = Concurrent::Hash.new
|
|
10
|
+
@threads = Concurrent::Hash.new
|
|
11
|
+
@running = Concurrent::AtomicBoolean.new(true)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(key:, &blk)
|
|
15
|
+
ensure_worker_running!(key)
|
|
16
|
+
|
|
17
|
+
future = Concurrent::Promises.resolvable_future
|
|
18
|
+
@queues[key] << [blk, future]
|
|
19
|
+
future
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def shutdown
|
|
23
|
+
@running.make_false
|
|
24
|
+
@threads.each_value do |thread|
|
|
25
|
+
thread.join(2)
|
|
26
|
+
thread.kill if thread.alive?
|
|
27
|
+
end
|
|
28
|
+
@threads.clear
|
|
29
|
+
@queues.clear
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def ensure_worker_running!(key)
|
|
35
|
+
return if @threads.key?(key) && @threads[key].alive?
|
|
36
|
+
|
|
37
|
+
@queues[key] ||= Queue.new
|
|
38
|
+
@threads[key] = Thread.new { worker_loop(key) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def worker_loop(key)
|
|
42
|
+
queue = @queues[key]
|
|
43
|
+
loop do
|
|
44
|
+
break unless @running.true?
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
blk, future = queue.pop(true)
|
|
48
|
+
result = blk.call
|
|
49
|
+
if result.is_a?(Concurrent::Promises::Future)
|
|
50
|
+
result.then { |v| future.fulfill(v) }.rescue { |e| future.reject(e) }
|
|
51
|
+
else
|
|
52
|
+
future.fulfill(result)
|
|
53
|
+
end
|
|
54
|
+
rescue ThreadError
|
|
55
|
+
sleep 0.01
|
|
56
|
+
rescue => e
|
|
57
|
+
future&.reject(e) if future
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
require_relative 'async/request'
|
|
5
|
+
require_relative 'async/request_queue'
|
|
6
|
+
require_relative 'async/middleware/base'
|
|
7
|
+
require_relative 'async/middleware/flood_wait'
|
|
8
|
+
|
|
9
|
+
module MTProto
|
|
10
|
+
class AsyncClient
|
|
11
|
+
attr_reader :client
|
|
12
|
+
|
|
13
|
+
def initialize(client)
|
|
14
|
+
@client = client
|
|
15
|
+
@queue = Async::RequestQueue.new
|
|
16
|
+
@middleware_classes = []
|
|
17
|
+
@pending_requests = Concurrent::Hash.new
|
|
18
|
+
@timeout_threads = Concurrent::Hash.new
|
|
19
|
+
@receiver_thread = nil
|
|
20
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
|
21
|
+
|
|
22
|
+
use(Async::Middleware::FloodWait)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def use(middleware_class, **options)
|
|
26
|
+
@middleware_classes << [middleware_class, options]
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def call(body, metadata: {})
|
|
31
|
+
ensure_receiver_running!
|
|
32
|
+
|
|
33
|
+
request = Async::Request.new(body: body, metadata: metadata)
|
|
34
|
+
|
|
35
|
+
@queue.call(key: request.key) do
|
|
36
|
+
send_and_wrap = lambda do |retries = 0|
|
|
37
|
+
base_future = send_request(request.body)
|
|
38
|
+
|
|
39
|
+
retry_fn = lambda do
|
|
40
|
+
send_and_wrap.call(retries + 1)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
build_middleware_stack.call(request, base_future, retry_fn, retries)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
send_and_wrap.call(0)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def shutdown
|
|
51
|
+
@running.make_false
|
|
52
|
+
|
|
53
|
+
@client.disconnect! if @client.connected?
|
|
54
|
+
|
|
55
|
+
if @receiver_thread
|
|
56
|
+
@receiver_thread.join(2)
|
|
57
|
+
@receiver_thread.kill if @receiver_thread.alive?
|
|
58
|
+
@receiver_thread = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@timeout_threads.each_value(&:kill)
|
|
62
|
+
@timeout_threads.clear
|
|
63
|
+
|
|
64
|
+
@pending_requests.each_value do |future|
|
|
65
|
+
future.reject(MTProto::Transport::ConnectionError.new('Client shutting down')) unless future.resolved?
|
|
66
|
+
end
|
|
67
|
+
@pending_requests.clear
|
|
68
|
+
|
|
69
|
+
@queue.shutdown
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def send_request(body)
|
|
75
|
+
msg_id = @client.rpc.call(body)
|
|
76
|
+
future = Concurrent::Promises.resolvable_future
|
|
77
|
+
@pending_requests[msg_id] = future
|
|
78
|
+
|
|
79
|
+
timeout_thread = Thread.new do
|
|
80
|
+
sleep @client.timeout
|
|
81
|
+
if @pending_requests.delete(msg_id)
|
|
82
|
+
future.reject(MTProto::Transport::ConnectionError.new('RPC timeout'))
|
|
83
|
+
end
|
|
84
|
+
@timeout_threads.delete(msg_id)
|
|
85
|
+
end
|
|
86
|
+
@timeout_threads[msg_id] = timeout_thread
|
|
87
|
+
|
|
88
|
+
future.on_fulfillment! do
|
|
89
|
+
timeout_thread.kill
|
|
90
|
+
@timeout_threads.delete(msg_id)
|
|
91
|
+
end
|
|
92
|
+
future.on_rejection! do
|
|
93
|
+
timeout_thread.kill
|
|
94
|
+
@timeout_threads.delete(msg_id)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
future
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def ensure_receiver_running!
|
|
101
|
+
return if @running.true?
|
|
102
|
+
|
|
103
|
+
@running.make_true
|
|
104
|
+
@receiver_thread = Thread.new { receiver_loop }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def receiver_loop
|
|
108
|
+
loop do
|
|
109
|
+
break unless @running.true?
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
response_data = @client.connection.recv(timeout: 1)
|
|
113
|
+
|
|
114
|
+
decrypted = EncryptedMessage.decrypt(
|
|
115
|
+
auth_key: @client.auth_key,
|
|
116
|
+
encrypted_message_data: response_data,
|
|
117
|
+
sender: :server
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
process_message(decrypted[:body])
|
|
121
|
+
rescue MTProto::Transport::ConnectionError => e
|
|
122
|
+
next if e.message == 'Receive timeout' && @running.true?
|
|
123
|
+
break
|
|
124
|
+
rescue IOError => e
|
|
125
|
+
break
|
|
126
|
+
rescue => e
|
|
127
|
+
warn "AsyncClient receiver error: #{e.class}: #{e.message}"
|
|
128
|
+
warn e.backtrace.join("\n")
|
|
129
|
+
break
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def process_message(response_body)
|
|
135
|
+
constructor = response_body[0, 4].unpack1('L<')
|
|
136
|
+
|
|
137
|
+
case constructor
|
|
138
|
+
when Client::RPC::CONSTRUCTOR_BAD_SERVER_SALT
|
|
139
|
+
handle_bad_server_salt(response_body)
|
|
140
|
+
when Type::NewSessionCreated::CONSTRUCTOR
|
|
141
|
+
handle_new_session(response_body)
|
|
142
|
+
when Type::MsgContainer::CONSTRUCTOR
|
|
143
|
+
handle_container(response_body)
|
|
144
|
+
when Client::RPC::CONSTRUCTOR_RPC_RESULT
|
|
145
|
+
handle_rpc_result(response_body)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def handle_bad_server_salt(response_body)
|
|
150
|
+
offset = 4
|
|
151
|
+
offset += 8
|
|
152
|
+
offset += 4
|
|
153
|
+
offset += 4
|
|
154
|
+
new_server_salt = response_body[offset, 8].unpack1('Q<')
|
|
155
|
+
|
|
156
|
+
@client.instance_variable_set(:@server_salt, new_server_salt)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def handle_new_session(response_body)
|
|
160
|
+
session_info = Type::NewSessionCreated.deserialize(response_body)
|
|
161
|
+
@client.instance_variable_set(:@server_salt, session_info.server_salt)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def handle_container(response_body)
|
|
165
|
+
container = Type::MsgContainer.deserialize(response_body)
|
|
166
|
+
|
|
167
|
+
container.messages.each do |msg|
|
|
168
|
+
process_message(msg[:body])
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def handle_rpc_result(response_body)
|
|
173
|
+
offset = 4
|
|
174
|
+
req_msg_id = response_body[offset, 8].unpack1('Q<')
|
|
175
|
+
offset += 8
|
|
176
|
+
result = response_body[offset..]
|
|
177
|
+
|
|
178
|
+
future = @pending_requests.delete(req_msg_id)
|
|
179
|
+
return unless future # No pending request for this msg_id
|
|
180
|
+
|
|
181
|
+
# Check if result is an RPC error
|
|
182
|
+
result_constructor = result[0, 4].unpack1('L<')
|
|
183
|
+
if result_constructor == Type::RpcError::CONSTRUCTOR
|
|
184
|
+
error = Type::RpcError.deserialize(result)
|
|
185
|
+
future.reject(MTProto::RpcError.new(error.error_code, error.error_message))
|
|
186
|
+
else
|
|
187
|
+
future.fulfill(result)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def build_middleware_stack
|
|
192
|
+
# Middleware now wraps Futures and can trigger retries
|
|
193
|
+
# The base "app" just returns the Future as-is
|
|
194
|
+
app = ->(request, future, retry_fn, retries) { future }
|
|
195
|
+
|
|
196
|
+
@middleware_classes.reverse.reduce(app) do |stack, (middleware_class, options)|
|
|
197
|
+
middleware_class.new(stack, **options)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -2,6 +2,13 @@
|
|
|
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
12
|
|
|
6
13
|
module MTProto
|
|
7
14
|
class AuthKeyGenerator
|
|
@@ -80,12 +87,13 @@ module MTProto
|
|
|
80
87
|
|
|
81
88
|
def req_pq_multi
|
|
82
89
|
nonce = SecureRandom.random_bytes(16)
|
|
83
|
-
|
|
90
|
+
body = Type::AuthKey::ReqPqMulti.build(nonce)
|
|
91
|
+
message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
|
|
84
92
|
@connection.send(message.serialize)
|
|
85
93
|
|
|
86
94
|
response_data = @connection.recv(timeout: @timeout)
|
|
87
|
-
response_message =
|
|
88
|
-
res_pq = response_message.
|
|
95
|
+
response_message = Type::Message.deserialize(response_data)
|
|
96
|
+
res_pq = Type::AuthKey::ResPq.parse(response_message.body)
|
|
89
97
|
|
|
90
98
|
raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
|
|
91
99
|
|
|
@@ -106,7 +114,7 @@ module MTProto
|
|
|
106
114
|
# For production DCs, use the DC number as-is
|
|
107
115
|
dc_value = @test_mode ? (@dc_number + 10000) : @dc_number
|
|
108
116
|
|
|
109
|
-
inner_data =
|
|
117
|
+
inner_data = Type::PQInnerData.new(
|
|
110
118
|
pq: Crypto::Factorization.bytes_to_integer(res_pq[:pq]),
|
|
111
119
|
p: p,
|
|
112
120
|
q: q,
|
|
@@ -120,7 +128,7 @@ module MTProto
|
|
|
120
128
|
end
|
|
121
129
|
|
|
122
130
|
def send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
|
|
123
|
-
|
|
131
|
+
body = Type::AuthKey::ReqDHParams.build(
|
|
124
132
|
nonce: res_pq[:nonce],
|
|
125
133
|
server_nonce: res_pq[:server_nonce],
|
|
126
134
|
p: p,
|
|
@@ -128,12 +136,13 @@ module MTProto
|
|
|
128
136
|
public_key_fingerprint: server_key.fingerprint,
|
|
129
137
|
encrypted_data: encrypted_data
|
|
130
138
|
)
|
|
139
|
+
message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
|
|
131
140
|
|
|
132
141
|
@connection.send(message.serialize)
|
|
133
142
|
|
|
134
143
|
response_data = @connection.recv(timeout: @timeout)
|
|
135
|
-
response_message =
|
|
136
|
-
server_dh_params = response_message.
|
|
144
|
+
response_message = Type::Message.deserialize(response_data)
|
|
145
|
+
server_dh_params = Type::AuthKey::ServerDHParams.parse(response_message.body)
|
|
137
146
|
|
|
138
147
|
raise 'Nonce mismatch!' unless server_dh_params[:nonce] == res_pq[:nonce]
|
|
139
148
|
raise 'Server nonce mismatch!' unless server_dh_params[:server_nonce] == res_pq[:server_nonce]
|
|
@@ -163,7 +172,7 @@ module MTProto
|
|
|
163
172
|
answer = answer[0..-2]
|
|
164
173
|
end
|
|
165
174
|
|
|
166
|
-
server_dh_inner_data =
|
|
175
|
+
server_dh_inner_data = Type::ServerDHInnerData.deserialize(answer)
|
|
167
176
|
|
|
168
177
|
raise 'Nonce mismatch in DH inner data!' unless server_dh_inner_data.nonce == res_pq[:nonce]
|
|
169
178
|
raise 'Server nonce mismatch in DH inner data!' unless server_dh_inner_data.server_nonce == res_pq[:server_nonce]
|
|
@@ -172,7 +181,7 @@ module MTProto
|
|
|
172
181
|
end
|
|
173
182
|
|
|
174
183
|
def send_client_dh_params(res_pq, new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
|
|
175
|
-
client_dh_inner_data =
|
|
184
|
+
client_dh_inner_data = Type::ClientDHInnerData.new(
|
|
176
185
|
nonce: res_pq[:nonce],
|
|
177
186
|
server_nonce: res_pq[:server_nonce],
|
|
178
187
|
retry_id: 0,
|
|
@@ -191,17 +200,18 @@ module MTProto
|
|
|
191
200
|
tmp_aes_iv
|
|
192
201
|
)
|
|
193
202
|
|
|
194
|
-
|
|
203
|
+
body = Type::AuthKey::SetClientDHParams.build(
|
|
195
204
|
nonce: res_pq[:nonce],
|
|
196
205
|
server_nonce: res_pq[:server_nonce],
|
|
197
206
|
encrypted_data: client_dh_encrypted
|
|
198
207
|
)
|
|
208
|
+
message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
|
|
199
209
|
|
|
200
210
|
@connection.send(message.serialize)
|
|
201
211
|
|
|
202
212
|
response_data = @connection.recv(timeout: @timeout)
|
|
203
|
-
response_message =
|
|
204
|
-
response_message.
|
|
213
|
+
response_message = Type::Message.deserialize(response_data)
|
|
214
|
+
Type::AuthKey::DHGenResponse.parse(response_message.body)
|
|
205
215
|
end
|
|
206
216
|
|
|
207
217
|
def verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
msg_id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call_sync(body, content_related: true)
|
|
39
|
+
raise 'Auth key not generated' unless @client.auth_key
|
|
40
|
+
raise 'Session not initialized' unless @client.session
|
|
41
|
+
|
|
42
|
+
msg_id = MessageId.generate(time_offset: @client.time_offset)
|
|
43
|
+
seq_no = @client.session.next_seq_no(content_related: content_related)
|
|
44
|
+
|
|
45
|
+
encrypted_msg = EncryptedMessage.encrypt(
|
|
46
|
+
auth_key: @client.auth_key,
|
|
47
|
+
server_salt: @client.server_salt,
|
|
48
|
+
session_id: @client.session.session_id,
|
|
49
|
+
msg_id: msg_id,
|
|
50
|
+
seq_no: seq_no,
|
|
51
|
+
body: body
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@client.connection.send(encrypted_msg.serialize)
|
|
55
|
+
|
|
56
|
+
response_data = @client.connection.recv(timeout: @client.timeout)
|
|
57
|
+
|
|
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<')
|
|
67
|
+
|
|
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<')
|
|
74
|
+
|
|
75
|
+
update_server_salt(new_server_salt)
|
|
76
|
+
return call_sync(body, content_related: content_related)
|
|
77
|
+
end
|
|
78
|
+
|
|
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
|
|
92
|
+
|
|
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]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
|
|
145
|
+
|
|
146
|
+
response_body
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
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)
|
|
156
|
+
end
|
|
157
|
+
result
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def update_server_salt(server_salt)
|
|
161
|
+
@client.instance_variable_set(:@server_salt, server_salt)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|