mtproto 0.0.7 → 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 +21 -21
- data/lib/mtproto/client/rpc.rb +44 -20
- data/lib/mtproto/client.rb +49 -15
- data/lib/mtproto/rpc/get_config.rb +19 -22
- data/lib/mtproto/rpc/get_contacts.rb +12 -5
- data/lib/mtproto/rpc/get_updates_difference.rb +23 -5
- data/lib/mtproto/rpc/get_updates_state.rb +12 -5
- data/lib/mtproto/rpc/get_users.rb +12 -5
- data/lib/mtproto/rpc/ping.rb +12 -5
- data/lib/mtproto/rpc/send_code.rb +19 -22
- data/lib/mtproto/rpc/send_message.rb +21 -5
- data/lib/mtproto/rpc/sign_in.rb +23 -27
- data/lib/mtproto/{tl → type}/auth_key/dh_gen_response.rb +1 -1
- data/lib/mtproto/{tl → type}/auth_key/req_dh_params.rb +1 -1
- data/lib/mtproto/{tl → type}/auth_key/req_pq_multi.rb +1 -1
- data/lib/mtproto/{tl → type}/auth_key/res_pq.rb +1 -1
- data/lib/mtproto/{tl → type}/auth_key/server_dh_params.rb +1 -1
- data/lib/mtproto/{tl → type}/auth_key/set_client_dh_params.rb +1 -1
- 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/{tl → type}/message.rb +1 -1
- 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/{tl → type}/rpc/auth/authorization.rb +5 -5
- data/lib/mtproto/{tl → type}/rpc/auth/send_code.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/auth/sent_code.rb +7 -7
- data/lib/mtproto/{tl → type}/rpc/auth/sign_in.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/contacts/contacts.rb +5 -5
- data/lib/mtproto/{tl → type}/rpc/contacts/get_contacts.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/help/config.rb +7 -7
- data/lib/mtproto/{tl → type}/rpc/help/get_config.rb +1 -1
- data/lib/mtproto/type/rpc/init_connection.rb +28 -0
- data/lib/mtproto/type/rpc/invoke_with_layer.rb +19 -0
- data/lib/mtproto/{tl → type}/rpc/messages/send_message.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/messages/updates.rb +5 -5
- data/lib/mtproto/{tl → type}/rpc/ping.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/pong.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/updates/difference.rb +5 -5
- data/lib/mtproto/{tl → type}/rpc/updates/get_difference.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/updates/get_state.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/updates/state.rb +5 -5
- data/lib/mtproto/{tl → type}/rpc/users/get_users.rb +1 -1
- data/lib/mtproto/{tl → type}/rpc/users/users.rb +5 -5
- 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 +3 -3
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +18 -13
- metadata +59 -39
- data/lib/mtproto/tl/method_builder.rb +0 -29
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
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
require 'securerandom'
|
|
4
4
|
require 'digest'
|
|
5
5
|
require_relative 'message_id'
|
|
6
|
-
require_relative '
|
|
7
|
-
require_relative '
|
|
8
|
-
require_relative '
|
|
9
|
-
require_relative '
|
|
10
|
-
require_relative '
|
|
11
|
-
require_relative '
|
|
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'
|
|
12
12
|
|
|
13
13
|
module MTProto
|
|
14
14
|
class AuthKeyGenerator
|
|
@@ -87,13 +87,13 @@ module MTProto
|
|
|
87
87
|
|
|
88
88
|
def req_pq_multi
|
|
89
89
|
nonce = SecureRandom.random_bytes(16)
|
|
90
|
-
body =
|
|
91
|
-
message =
|
|
90
|
+
body = Type::AuthKey::ReqPqMulti.build(nonce)
|
|
91
|
+
message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
|
|
92
92
|
@connection.send(message.serialize)
|
|
93
93
|
|
|
94
94
|
response_data = @connection.recv(timeout: @timeout)
|
|
95
|
-
response_message =
|
|
96
|
-
res_pq =
|
|
95
|
+
response_message = Type::Message.deserialize(response_data)
|
|
96
|
+
res_pq = Type::AuthKey::ResPq.parse(response_message.body)
|
|
97
97
|
|
|
98
98
|
raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
|
|
99
99
|
|
|
@@ -114,7 +114,7 @@ module MTProto
|
|
|
114
114
|
# For production DCs, use the DC number as-is
|
|
115
115
|
dc_value = @test_mode ? (@dc_number + 10000) : @dc_number
|
|
116
116
|
|
|
117
|
-
inner_data =
|
|
117
|
+
inner_data = Type::PQInnerData.new(
|
|
118
118
|
pq: Crypto::Factorization.bytes_to_integer(res_pq[:pq]),
|
|
119
119
|
p: p,
|
|
120
120
|
q: q,
|
|
@@ -128,7 +128,7 @@ module MTProto
|
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
def send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
|
|
131
|
-
body =
|
|
131
|
+
body = Type::AuthKey::ReqDHParams.build(
|
|
132
132
|
nonce: res_pq[:nonce],
|
|
133
133
|
server_nonce: res_pq[:server_nonce],
|
|
134
134
|
p: p,
|
|
@@ -136,13 +136,13 @@ module MTProto
|
|
|
136
136
|
public_key_fingerprint: server_key.fingerprint,
|
|
137
137
|
encrypted_data: encrypted_data
|
|
138
138
|
)
|
|
139
|
-
message =
|
|
139
|
+
message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
|
|
140
140
|
|
|
141
141
|
@connection.send(message.serialize)
|
|
142
142
|
|
|
143
143
|
response_data = @connection.recv(timeout: @timeout)
|
|
144
|
-
response_message =
|
|
145
|
-
server_dh_params =
|
|
144
|
+
response_message = Type::Message.deserialize(response_data)
|
|
145
|
+
server_dh_params = Type::AuthKey::ServerDHParams.parse(response_message.body)
|
|
146
146
|
|
|
147
147
|
raise 'Nonce mismatch!' unless server_dh_params[:nonce] == res_pq[:nonce]
|
|
148
148
|
raise 'Server nonce mismatch!' unless server_dh_params[:server_nonce] == res_pq[:server_nonce]
|
|
@@ -172,7 +172,7 @@ module MTProto
|
|
|
172
172
|
answer = answer[0..-2]
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
server_dh_inner_data =
|
|
175
|
+
server_dh_inner_data = Type::ServerDHInnerData.deserialize(answer)
|
|
176
176
|
|
|
177
177
|
raise 'Nonce mismatch in DH inner data!' unless server_dh_inner_data.nonce == res_pq[:nonce]
|
|
178
178
|
raise 'Server nonce mismatch in DH inner data!' unless server_dh_inner_data.server_nonce == res_pq[:server_nonce]
|
|
@@ -181,7 +181,7 @@ module MTProto
|
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
def send_client_dh_params(res_pq, new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
|
|
184
|
-
client_dh_inner_data =
|
|
184
|
+
client_dh_inner_data = Type::ClientDHInnerData.new(
|
|
185
185
|
nonce: res_pq[:nonce],
|
|
186
186
|
server_nonce: res_pq[:server_nonce],
|
|
187
187
|
retry_id: 0,
|
|
@@ -200,18 +200,18 @@ module MTProto
|
|
|
200
200
|
tmp_aes_iv
|
|
201
201
|
)
|
|
202
202
|
|
|
203
|
-
body =
|
|
203
|
+
body = Type::AuthKey::SetClientDHParams.build(
|
|
204
204
|
nonce: res_pq[:nonce],
|
|
205
205
|
server_nonce: res_pq[:server_nonce],
|
|
206
206
|
encrypted_data: client_dh_encrypted
|
|
207
207
|
)
|
|
208
|
-
message =
|
|
208
|
+
message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
|
|
209
209
|
|
|
210
210
|
@connection.send(message.serialize)
|
|
211
211
|
|
|
212
212
|
response_data = @connection.recv(timeout: @timeout)
|
|
213
|
-
response_message =
|
|
214
|
-
|
|
213
|
+
response_message = Type::Message.deserialize(response_data)
|
|
214
|
+
Type::AuthKey::DHGenResponse.parse(response_message.body)
|
|
215
215
|
end
|
|
216
216
|
|
|
217
217
|
def verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
|
data/lib/mtproto/client/rpc.rb
CHANGED
|
@@ -32,6 +32,27 @@ module MTProto
|
|
|
32
32
|
|
|
33
33
|
@client.connection.send(encrypted_msg.serialize)
|
|
34
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
|
+
|
|
35
56
|
response_data = @client.connection.recv(timeout: @client.timeout)
|
|
36
57
|
|
|
37
58
|
decrypted = EncryptedMessage.decrypt(
|
|
@@ -44,25 +65,19 @@ module MTProto
|
|
|
44
65
|
|
|
45
66
|
constructor = response_body[0, 4].unpack1('L<')
|
|
46
67
|
|
|
47
|
-
# Handle bad_server_salt
|
|
48
68
|
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
69
|
offset = 4
|
|
51
|
-
bad_msg_id = response_body[offset, 8].unpack1('Q<')
|
|
52
70
|
offset += 8
|
|
53
|
-
bad_msg_seqno = response_body[offset, 4].unpack1('L<')
|
|
54
71
|
offset += 4
|
|
55
|
-
error_code = response_body[offset, 4].unpack1('L<')
|
|
56
72
|
offset += 4
|
|
57
73
|
new_server_salt = response_body[offset, 8].unpack1('Q<')
|
|
58
74
|
|
|
59
|
-
# Update server salt and retry
|
|
60
75
|
update_server_salt(new_server_salt)
|
|
61
|
-
return
|
|
76
|
+
return call_sync(body, content_related: content_related)
|
|
62
77
|
end
|
|
63
78
|
|
|
64
|
-
if constructor ==
|
|
65
|
-
session_info =
|
|
79
|
+
if constructor == Type::NewSessionCreated::CONSTRUCTOR
|
|
80
|
+
session_info = Type::NewSessionCreated.deserialize(response_body)
|
|
66
81
|
update_server_salt(session_info.server_salt)
|
|
67
82
|
|
|
68
83
|
response_data = @client.connection.recv(timeout: @client.timeout)
|
|
@@ -75,26 +90,26 @@ module MTProto
|
|
|
75
90
|
constructor = response_body[0, 4].unpack1('L<')
|
|
76
91
|
end
|
|
77
92
|
|
|
78
|
-
if constructor ==
|
|
79
|
-
container =
|
|
93
|
+
if constructor == Type::MsgContainer::CONSTRUCTOR
|
|
94
|
+
container = Type::MsgContainer.deserialize(response_body)
|
|
80
95
|
|
|
81
96
|
rpc_result = container.messages.find do |msg|
|
|
82
97
|
msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
|
|
83
98
|
end
|
|
84
99
|
|
|
85
|
-
return rpc_result[:body][12..] if rpc_result
|
|
100
|
+
return extract_rpc_result(rpc_result[:body][12..]) if rpc_result
|
|
86
101
|
|
|
87
102
|
new_session = container.messages.find do |msg|
|
|
88
|
-
msg[:body][0, 4].unpack1('L<') ==
|
|
103
|
+
msg[:body][0, 4].unpack1('L<') == Type::NewSessionCreated::CONSTRUCTOR
|
|
89
104
|
end
|
|
90
105
|
if new_session
|
|
91
|
-
session_info =
|
|
106
|
+
session_info = Type::NewSessionCreated.deserialize(new_session[:body])
|
|
92
107
|
update_server_salt(session_info.server_salt)
|
|
93
108
|
|
|
94
109
|
other_messages = container.messages.reject do |msg|
|
|
95
110
|
constructor = msg[:body][0, 4].unpack1('L<')
|
|
96
111
|
(
|
|
97
|
-
constructor ==
|
|
112
|
+
constructor == Type::NewSessionCreated::CONSTRUCTOR ||
|
|
98
113
|
constructor == CONSTRUCTOR_MSGS_ACK
|
|
99
114
|
)
|
|
100
115
|
end
|
|
@@ -110,14 +125,14 @@ module MTProto
|
|
|
110
125
|
response_body = decrypted[:body]
|
|
111
126
|
constructor = response_body[0, 4].unpack1('L<')
|
|
112
127
|
|
|
113
|
-
return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
|
|
128
|
+
return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
|
|
114
129
|
|
|
115
|
-
if constructor ==
|
|
116
|
-
container =
|
|
130
|
+
if constructor == Type::MsgContainer::CONSTRUCTOR
|
|
131
|
+
container = Type::MsgContainer.deserialize(response_body)
|
|
117
132
|
rpc_result = container.messages.find do |msg|
|
|
118
133
|
msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
|
|
119
134
|
end
|
|
120
|
-
return rpc_result[:body][12..] if rpc_result
|
|
135
|
+
return extract_rpc_result(rpc_result[:body][12..]) if rpc_result
|
|
121
136
|
end
|
|
122
137
|
|
|
123
138
|
return response_body
|
|
@@ -126,13 +141,22 @@ module MTProto
|
|
|
126
141
|
return container.messages.first[:body]
|
|
127
142
|
end
|
|
128
143
|
|
|
129
|
-
return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
|
|
144
|
+
return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
|
|
130
145
|
|
|
131
146
|
response_body
|
|
132
147
|
end
|
|
133
148
|
|
|
134
149
|
private
|
|
135
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
|
+
|
|
136
160
|
def update_server_salt(server_salt)
|
|
137
161
|
@client.instance_variable_set(:@server_salt, server_salt)
|
|
138
162
|
end
|