mtproto 0.0.8 → 0.0.10
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/data/tl-schema.json +42686 -0
- data/ext/aes_ige/extconf.rb +3 -9
- data/ext/factorization/extconf.rb +2 -0
- data/lib/mtproto/auth_key_generator.rb +68 -105
- data/lib/mtproto/binary.rb +21 -0
- data/lib/mtproto/client/api/check_password.rb +41 -0
- data/lib/mtproto/client/api/export_login_token.rb +27 -0
- data/lib/mtproto/client/api/get_dialogs.rb +21 -0
- data/lib/mtproto/client/api/get_history.rb +20 -0
- data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
- data/lib/mtproto/client/api/get_updates_state.rb +14 -0
- data/lib/mtproto/client/api/get_users.rb +14 -0
- data/lib/mtproto/client/api/import_login_token.rb +23 -0
- data/lib/mtproto/client/api/send_code.rb +21 -0
- data/lib/mtproto/client/api/sign_in.rb +27 -0
- data/lib/mtproto/client/api.rb +36 -0
- data/lib/mtproto/client/rpc/response.rb +63 -0
- data/lib/mtproto/client/rpc.rb +60 -127
- data/lib/mtproto/client.rb +143 -32
- data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
- data/lib/mtproto/crypto/dh_validator.rb +17 -19
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/crypto/rsa_key.rb +2 -2
- data/lib/mtproto/crypto/srp.rb +117 -0
- data/lib/mtproto/delegate_methods.rb +11 -0
- data/lib/mtproto/errors.rb +8 -0
- data/lib/mtproto/message/message.rb +85 -0
- data/lib/mtproto/session.rb +1 -1
- data/lib/mtproto/tl/constructor_names.rb +2271 -0
- data/lib/mtproto/tl/constructors.rb +99 -0
- data/lib/mtproto/tl/object.rb +25 -0
- data/lib/mtproto/tl/objects/account_password.rb +69 -0
- data/lib/mtproto/tl/objects/authorization.rb +70 -0
- data/lib/mtproto/tl/objects/check_password.rb +43 -0
- data/lib/mtproto/tl/objects/client_dh_inner_data.rb +45 -0
- data/lib/mtproto/tl/objects/dh_gen_response.rb +46 -0
- data/lib/mtproto/tl/objects/dialogs.rb +453 -0
- data/lib/mtproto/tl/objects/export_login_token.rb +48 -0
- data/lib/mtproto/tl/objects/get_config.rb +13 -0
- data/lib/mtproto/tl/objects/get_dialogs.rb +51 -0
- data/lib/mtproto/tl/objects/get_difference.rb +34 -0
- data/lib/mtproto/tl/objects/get_history.rb +49 -0
- data/lib/mtproto/tl/objects/get_password.rb +13 -0
- data/lib/mtproto/tl/objects/get_state.rb +13 -0
- data/lib/mtproto/tl/objects/get_users.rb +16 -0
- data/lib/mtproto/{type → tl/objects}/gzip_packed.rb +6 -6
- data/lib/mtproto/tl/objects/help_config.rb +76 -0
- data/lib/mtproto/tl/objects/import_login_token.rb +37 -0
- data/lib/mtproto/tl/objects/init_connection.rb +57 -0
- data/lib/mtproto/tl/objects/invoke_with_layer.rb +20 -0
- data/lib/mtproto/tl/objects/login_token.rb +78 -0
- data/lib/mtproto/{type → tl/objects}/message.rb +3 -3
- data/lib/mtproto/tl/objects/messages.rb +162 -0
- data/lib/mtproto/{type → tl/objects}/msg_container.rb +1 -3
- data/lib/mtproto/{type → tl/objects}/new_session_created.rb +1 -3
- data/lib/mtproto/tl/objects/pq_inner_data.rb +66 -0
- data/lib/mtproto/tl/objects/req_dh_params.rb +63 -0
- data/lib/mtproto/tl/objects/req_pq_multi.rb +21 -0
- data/lib/mtproto/tl/objects/res_pq.rb +73 -0
- data/lib/mtproto/{type → tl/objects}/rpc_error.rb +1 -4
- data/lib/mtproto/tl/objects/send_code.rb +47 -0
- data/lib/mtproto/tl/objects/sent_code.rb +79 -0
- data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
- data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
- data/lib/mtproto/tl/objects/set_client_dh_params.rb +46 -0
- data/lib/mtproto/tl/objects/sign_in.rb +45 -0
- data/lib/mtproto/tl/objects/update.rb +77 -0
- data/lib/mtproto/tl/objects/update_short.rb +20 -0
- data/lib/mtproto/tl/objects/update_short_message.rb +65 -0
- data/lib/mtproto/tl/objects/updates_difference.rb +152 -0
- data/lib/mtproto/tl/objects/updates_state.rb +35 -0
- data/lib/mtproto/tl/objects/users.rb +83 -0
- data/lib/mtproto/tl/schema.rb +102 -0
- data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
- data/lib/mtproto/transport/connection.rb +23 -0
- data/lib/mtproto/transport/errors.rb +11 -0
- data/lib/mtproto/transport/packet.rb +19 -0
- data/lib/mtproto/transport/tcp_connection.rb +57 -46
- data/lib/mtproto/updates_poller.rb +37 -33
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +17 -27
- data/scripts/generate_constructors.rb +65 -0
- metadata +76 -61
- data/lib/mtproto/async/middleware/base.rb +0 -17
- data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
- data/lib/mtproto/async/request.rb +0 -18
- data/lib/mtproto/async/request_queue.rb +0 -63
- data/lib/mtproto/async_client.rb +0 -201
- data/lib/mtproto/rpc/get_config.rb +0 -34
- data/lib/mtproto/rpc/get_contacts.rb +0 -29
- data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
- data/lib/mtproto/rpc/get_updates_state.rb +0 -29
- data/lib/mtproto/rpc/get_users.rb +0 -29
- data/lib/mtproto/rpc/ping.rb +0 -33
- data/lib/mtproto/rpc/send_code.rb +0 -41
- data/lib/mtproto/rpc/send_message.rb +0 -47
- data/lib/mtproto/rpc/sign_in.rb +0 -48
- data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
- data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
- data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
- data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
- data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
- data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
- data/lib/mtproto/type/bad_msg_notification.rb +0 -46
- data/lib/mtproto/type/client_dh_inner_data.rb +0 -29
- data/lib/mtproto/type/code_settings.rb +0 -25
- data/lib/mtproto/type/config.rb +0 -124
- data/lib/mtproto/type/pq_inner_data.rb +0 -41
- data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
- data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
- data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
- data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
- data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
- data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
- data/lib/mtproto/type/rpc/help/config.rb +0 -35
- data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
- data/lib/mtproto/type/rpc/init_connection.rb +0 -28
- data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
- data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
- data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
- data/lib/mtproto/type/rpc/ping.rb +0 -18
- data/lib/mtproto/type/rpc/pong.rb +0 -46
- data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
- data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
- data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
- data/lib/mtproto/type/rpc/updates/state.rb +0 -59
- data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
- data/lib/mtproto/type/rpc/users/users.rb +0 -99
- data/lib/mtproto/type/sent_code.rb +0 -128
- data/lib/mtproto/type/serializer.rb +0 -55
- data/lib/mtproto/type/server_dh_inner_data.rb +0 -85
data/lib/mtproto/client/rpc.rb
CHANGED
|
@@ -1,164 +1,97 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../message_id'
|
|
4
|
+
require_relative 'rpc/response'
|
|
4
5
|
|
|
5
6
|
module MTProto
|
|
6
7
|
class Client
|
|
7
8
|
class RPC
|
|
8
|
-
|
|
9
|
-
CONSTRUCTOR_MSGS_ACK = 0x62d6b459
|
|
10
|
-
CONSTRUCTOR_BAD_SERVER_SALT = 0xedab447b
|
|
9
|
+
attr_reader :pending_requests
|
|
11
10
|
|
|
12
11
|
def initialize(client)
|
|
13
12
|
@client = client
|
|
14
|
-
@
|
|
13
|
+
@pending_requests = {}
|
|
15
14
|
end
|
|
16
15
|
|
|
17
|
-
def call(
|
|
18
|
-
raise '
|
|
16
|
+
def call(request, response_class)
|
|
17
|
+
raise 'Mainloop not running' unless @client.mainloop_running?
|
|
18
|
+
raise 'Auth key not generated' unless @client.auth_key?
|
|
19
19
|
raise 'Session not initialized' unless @client.session
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
body = serialize_request(request)
|
|
22
|
+
msg_id = send_encrypted(body)
|
|
23
|
+
response = Response.new(msg_id, response_class, body)
|
|
24
|
+
@pending_requests[msg_id] = response
|
|
23
25
|
|
|
24
|
-
|
|
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
|
|
26
|
+
response
|
|
36
27
|
end
|
|
37
28
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
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)
|
|
29
|
+
def handle_bad_server_salt(response_body, client)
|
|
30
|
+
bad_msg_id = response_body[4, 8].unpack1('Q<')
|
|
31
|
+
new_server_salt = response_body[20, 8].unpack1('Q<')
|
|
55
32
|
|
|
56
|
-
|
|
33
|
+
client.update_server_salt(new_server_salt)
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
encrypted_message_data: response_data,
|
|
61
|
-
sender: :server
|
|
62
|
-
)
|
|
35
|
+
response = @pending_requests.delete(bad_msg_id)
|
|
36
|
+
return unless response
|
|
63
37
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
new_msg_id = send_encrypted(response.body_bytes)
|
|
39
|
+
response.instance_variable_set(:@msg_id, new_msg_id)
|
|
40
|
+
@pending_requests[new_msg_id] = response
|
|
41
|
+
end
|
|
67
42
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
offset += 4
|
|
72
|
-
offset += 4
|
|
73
|
-
new_server_salt = response_body[offset, 8].unpack1('Q<')
|
|
43
|
+
def handle_rpc_result(response_body)
|
|
44
|
+
req_msg_id = response_body[4, 8].unpack1('Q<')
|
|
45
|
+
result = response_body[12..]
|
|
74
46
|
|
|
75
|
-
|
|
76
|
-
|
|
47
|
+
result_constructor = result[0, 4].unpack1('L<')
|
|
48
|
+
if result_constructor == TL::Constructors::GZIP_PACKED
|
|
49
|
+
result = TL::GzipPacked.unpack(result)
|
|
50
|
+
result_constructor = result[0, 4].unpack1('L<')
|
|
77
51
|
end
|
|
78
52
|
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
53
|
+
response = @pending_requests.delete(req_msg_id)
|
|
54
|
+
return unless response
|
|
92
55
|
|
|
93
|
-
if
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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]
|
|
56
|
+
if result_constructor == TL::Constructors::RPC_ERROR
|
|
57
|
+
error = TL::RpcError.deserialize(result)
|
|
58
|
+
response.signal_error(RpcError.new(error.error_code, error.error_message))
|
|
59
|
+
else
|
|
60
|
+
response.signal(result)
|
|
142
61
|
end
|
|
62
|
+
end
|
|
143
63
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
64
|
+
def signal_all_error(error)
|
|
65
|
+
@pending_requests.each_value { |response| response.signal_error(error) }
|
|
66
|
+
@pending_requests.clear
|
|
147
67
|
end
|
|
148
68
|
|
|
149
69
|
private
|
|
150
70
|
|
|
151
|
-
def
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
71
|
+
def serialize_request(request)
|
|
72
|
+
if request.respond_to?(:body)
|
|
73
|
+
request.body.pack('C*')
|
|
74
|
+
else
|
|
75
|
+
request
|
|
156
76
|
end
|
|
157
|
-
result
|
|
158
77
|
end
|
|
159
78
|
|
|
160
|
-
def
|
|
161
|
-
@client.
|
|
79
|
+
def send_encrypted(body, content_related: true)
|
|
80
|
+
msg_id = MessageId.generate(time_offset: @client.time_offset)
|
|
81
|
+
seq_no = @client.session.next_seq_no(content_related: content_related)
|
|
82
|
+
|
|
83
|
+
encrypted_msg = EncryptedMessage.encrypt(
|
|
84
|
+
auth_key: @client.auth_key,
|
|
85
|
+
server_salt: @client.server_salt,
|
|
86
|
+
session_id: @client.session.session_id,
|
|
87
|
+
msg_id: msg_id,
|
|
88
|
+
seq_no: seq_no,
|
|
89
|
+
body: body
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@client.connection.send(Transport::Packet.new(encrypted_msg.serialize.bytes))
|
|
93
|
+
|
|
94
|
+
msg_id
|
|
162
95
|
end
|
|
163
96
|
end
|
|
164
97
|
end
|
data/lib/mtproto/client.rb
CHANGED
|
@@ -3,27 +3,59 @@
|
|
|
3
3
|
require 'securerandom'
|
|
4
4
|
require 'digest'
|
|
5
5
|
require 'base64'
|
|
6
|
+
require 'async'
|
|
7
|
+
require 'async/condition'
|
|
6
8
|
require_relative 'transport/tcp_connection'
|
|
9
|
+
require_relative 'transport/connection'
|
|
7
10
|
require_relative 'transport/abridged_packet_codec'
|
|
8
|
-
require_relative '
|
|
11
|
+
require_relative 'tl/objects/message'
|
|
9
12
|
require_relative 'client/rpc'
|
|
10
|
-
require_relative '
|
|
11
|
-
require_relative '
|
|
12
|
-
require_relative '
|
|
13
|
-
require_relative '
|
|
13
|
+
require_relative 'client/api'
|
|
14
|
+
require_relative 'tl/objects/invoke_with_layer'
|
|
15
|
+
require_relative 'tl/objects/init_connection'
|
|
16
|
+
require_relative 'tl/objects/get_config'
|
|
17
|
+
require_relative 'tl/objects/help_config'
|
|
14
18
|
|
|
15
19
|
module MTProto
|
|
16
20
|
class Client
|
|
21
|
+
extend DelegateMethods
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
attr_accessor :api_id, :api_hash, :device_model, :system_version, :app_version, :system_lang_code, :lang_pack, :lang_code
|
|
23
|
+
API_LAYER = 214
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
attr_reader :connection, :server_key, :auth_key, :server_salt, :time_offset, :session,
|
|
26
|
+
:timeout, :user_id, :access_hash, :dc_number
|
|
27
|
+
attr_accessor :api_id, :api_hash, :device_model, :system_version, :app_version,
|
|
28
|
+
:system_lang_code, :lang_pack, :lang_code
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
delegate :connect!, :connected?, to: :connection
|
|
31
|
+
|
|
32
|
+
def auth_key?
|
|
33
|
+
!@auth_key.nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def mainloop_running?
|
|
37
|
+
@running
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(
|
|
41
|
+
host:,
|
|
42
|
+
api_id:,
|
|
43
|
+
api_hash:,
|
|
44
|
+
port: 443,
|
|
45
|
+
public_key: nil,
|
|
46
|
+
dc_number: nil,
|
|
47
|
+
test_mode: false,
|
|
48
|
+
timeout: 10
|
|
49
|
+
)
|
|
50
|
+
raise ArgumentError, 'host is required' if host.nil? || host.empty?
|
|
51
|
+
|
|
52
|
+
raise ArgumentError, 'dc_number must be positive. Use test_mode: true for test DCs' if dc_number && dc_number < 0
|
|
53
|
+
|
|
54
|
+
@api_id = api_id
|
|
55
|
+
@api_hash = api_hash
|
|
56
|
+
|
|
57
|
+
transport = Transport::TCPConnection.new(host, port)
|
|
58
|
+
@connection = Transport::Connection.new(transport)
|
|
27
59
|
@public_key = public_key
|
|
28
60
|
@dc_number = dc_number
|
|
29
61
|
@test_mode = test_mode
|
|
@@ -37,33 +69,76 @@ module MTProto
|
|
|
37
69
|
@user_id = nil
|
|
38
70
|
@access_hash = nil
|
|
39
71
|
|
|
40
|
-
# Client configuration defaults
|
|
41
|
-
@api_id = api_id || 0
|
|
42
|
-
@api_hash = api_hash || ''
|
|
43
72
|
@device_model = 'Ruby MTProto'
|
|
44
73
|
@system_version = RUBY_DESCRIPTION
|
|
45
74
|
@app_version = '0.1.0'
|
|
46
75
|
@system_lang_code = 'en'
|
|
47
76
|
@lang_pack = ''
|
|
48
77
|
@lang_code = 'en'
|
|
78
|
+
|
|
79
|
+
@receiver_task = nil
|
|
80
|
+
@running = false
|
|
81
|
+
@on_update_callbacks = []
|
|
49
82
|
end
|
|
50
83
|
|
|
51
|
-
def
|
|
52
|
-
@
|
|
84
|
+
def on_update(&block)
|
|
85
|
+
@on_update_callbacks << block
|
|
53
86
|
end
|
|
54
87
|
|
|
55
|
-
def
|
|
56
|
-
|
|
88
|
+
def run_mainloop
|
|
89
|
+
raise 'Auth key not set' unless auth_key?
|
|
90
|
+
raise 'Mainloop already running' if @running
|
|
91
|
+
raise ArgumentError, 'Block is required' unless block_given?
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
Async do
|
|
95
|
+
@running = true
|
|
96
|
+
@receiver_task = Async do
|
|
97
|
+
@connection.receive do |packet, error|
|
|
98
|
+
if error
|
|
99
|
+
warn "[MTProto] Packet read error: #{error.message}"
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
decrypted = EncryptedMessage.decrypt(
|
|
104
|
+
auth_key: @auth_key,
|
|
105
|
+
encrypted_message_data: packet.data.pack('C*'),
|
|
106
|
+
sender: :server
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
process_message(decrypted[:body])
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
yield self
|
|
114
|
+
ensure
|
|
115
|
+
disconnect!
|
|
116
|
+
end
|
|
117
|
+
rescue Interrupt
|
|
118
|
+
# Ctrl+C
|
|
119
|
+
end
|
|
57
120
|
end
|
|
58
121
|
|
|
59
122
|
def disconnect!
|
|
60
|
-
@
|
|
123
|
+
@running = false
|
|
124
|
+
@receiver_task&.stop
|
|
125
|
+
@receiver_task = nil
|
|
126
|
+
|
|
127
|
+
rpc.signal_all_error(Transport::ConnectionError.new('Client shutting down'))
|
|
128
|
+
|
|
129
|
+
@connection.disconnect! if @connection&.connected?
|
|
61
130
|
end
|
|
62
131
|
|
|
63
132
|
def exchange_keys!
|
|
64
|
-
raise ArgumentError,
|
|
65
|
-
|
|
66
|
-
generator = AuthKeyGenerator.new(
|
|
133
|
+
raise ArgumentError, 'public_key is required for auth key generation' if @public_key.nil? || @public_key.empty?
|
|
134
|
+
|
|
135
|
+
generator = AuthKeyGenerator.new(
|
|
136
|
+
@connection,
|
|
137
|
+
@public_key,
|
|
138
|
+
@dc_number,
|
|
139
|
+
test_mode: @test_mode,
|
|
140
|
+
timeout: @timeout
|
|
141
|
+
)
|
|
67
142
|
result = generator.generate
|
|
68
143
|
|
|
69
144
|
@auth_key = generator.auth_key
|
|
@@ -78,16 +153,16 @@ module MTProto
|
|
|
78
153
|
@rpc ||= RPC.new(self)
|
|
79
154
|
end
|
|
80
155
|
|
|
81
|
-
def
|
|
82
|
-
|
|
156
|
+
def api
|
|
157
|
+
@api ||= API.new(self)
|
|
83
158
|
end
|
|
84
159
|
|
|
85
160
|
def init_connection!
|
|
86
161
|
return if @connection_initialized
|
|
87
162
|
|
|
88
|
-
query =
|
|
89
|
-
layer:
|
|
90
|
-
query:
|
|
163
|
+
query = TL::InvokeWithLayer.new(
|
|
164
|
+
layer: API_LAYER,
|
|
165
|
+
query: TL::InitConnection.new(
|
|
91
166
|
api_id: api_id,
|
|
92
167
|
device_model: device_model,
|
|
93
168
|
system_version: system_version,
|
|
@@ -95,16 +170,16 @@ module MTProto
|
|
|
95
170
|
system_lang_code: system_lang_code,
|
|
96
171
|
lang_pack: lang_pack,
|
|
97
172
|
lang_code: lang_code,
|
|
98
|
-
query:
|
|
173
|
+
query: TL::GetConfig.new
|
|
99
174
|
)
|
|
100
175
|
)
|
|
101
176
|
|
|
102
|
-
response = rpc.
|
|
103
|
-
|
|
177
|
+
response = rpc.call(query, TL::HelpConfig)
|
|
178
|
+
response.wait!(timeout)
|
|
104
179
|
|
|
105
180
|
@connection_initialized = true
|
|
106
181
|
|
|
107
|
-
|
|
182
|
+
response.body
|
|
108
183
|
end
|
|
109
184
|
|
|
110
185
|
def save_auth_data
|
|
@@ -140,5 +215,41 @@ module MTProto
|
|
|
140
215
|
@user_id = user_id
|
|
141
216
|
@access_hash = access_hash
|
|
142
217
|
end
|
|
218
|
+
|
|
219
|
+
def update_server_salt(new_salt)
|
|
220
|
+
@server_salt = new_salt
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def process_message(response_body)
|
|
226
|
+
constructor = response_body[0, 4].unpack1('L<')
|
|
227
|
+
|
|
228
|
+
case constructor
|
|
229
|
+
when TL::Constructors::BAD_SERVER_SALT
|
|
230
|
+
rpc.handle_bad_server_salt(response_body, self)
|
|
231
|
+
when TL::Constructors::NEW_SESSION_CREATED
|
|
232
|
+
handle_new_session(response_body)
|
|
233
|
+
when TL::Constructors::MSG_CONTAINER
|
|
234
|
+
handle_container(response_body)
|
|
235
|
+
when TL::Constructors::RPC_RESULT
|
|
236
|
+
rpc.handle_rpc_result(response_body)
|
|
237
|
+
else
|
|
238
|
+
@on_update_callbacks.each { |cb| cb.call(constructor, response_body) }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def handle_new_session(response_body)
|
|
243
|
+
session_info = TL::NewSessionCreated.deserialize(response_body)
|
|
244
|
+
@server_salt = session_info.server_salt
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def handle_container(response_body)
|
|
248
|
+
container = TL::MsgContainer.deserialize(response_body)
|
|
249
|
+
|
|
250
|
+
container.messages.each do |msg|
|
|
251
|
+
process_message(msg[:body])
|
|
252
|
+
end
|
|
253
|
+
end
|
|
143
254
|
end
|
|
144
255
|
end
|
|
@@ -22,22 +22,22 @@ module MTProto
|
|
|
22
22
|
raise 'Invalid g: must be 2, 3, 4, 5, 6, or 7' unless [2, 3, 4, 5, 6, 7].include?(g)
|
|
23
23
|
|
|
24
24
|
p_mod = case g
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
when 2
|
|
26
|
+
dh_prime % 8 == 7
|
|
27
|
+
when 3
|
|
28
|
+
dh_prime % 3 == 2
|
|
29
|
+
when 4
|
|
30
|
+
true
|
|
31
|
+
when 5
|
|
32
|
+
mod5 = dh_prime % 5
|
|
33
|
+
[1, 4].include?(mod5)
|
|
34
|
+
when 6
|
|
35
|
+
mod24 = dh_prime % 24
|
|
36
|
+
[19, 23].include?(mod24)
|
|
37
|
+
when 7
|
|
38
|
+
mod7 = dh_prime % 7
|
|
39
|
+
[3, 5, 6].include?(mod7)
|
|
40
|
+
end
|
|
41
41
|
|
|
42
42
|
raise "g=#{g} is not a valid generator for this prime" unless p_mod
|
|
43
43
|
|
|
@@ -52,9 +52,7 @@ module MTProto
|
|
|
52
52
|
min_prime = OpenSSL::BN.new(2)**2047
|
|
53
53
|
max_prime = OpenSSL::BN.new(2)**2048
|
|
54
54
|
|
|
55
|
-
if dh_prime <= min_prime || dh_prime >= max_prime
|
|
56
|
-
raise 'dh_prime out of range (must be 2^2047 < p < 2^2048)'
|
|
57
|
-
end
|
|
55
|
+
raise 'dh_prime out of range (must be 2^2047 < p < 2^2048)' if dh_prime <= min_prime || dh_prime >= max_prime
|
|
58
56
|
|
|
59
57
|
true
|
|
60
58
|
end
|
|
@@ -9,7 +9,7 @@ module MTProto
|
|
|
9
9
|
attr_reader :key, :fingerprint
|
|
10
10
|
|
|
11
11
|
def initialize(pem_string)
|
|
12
|
-
raise ArgumentError,
|
|
12
|
+
raise ArgumentError, 'pem_string is required' if pem_string.nil? || pem_string.empty?
|
|
13
13
|
|
|
14
14
|
@key = OpenSSL::PKey::RSA.new(pem_string)
|
|
15
15
|
@fingerprint = calculate_fingerprint
|
|
@@ -20,7 +20,7 @@ module MTProto
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def self.find_by_fingerprint(fingerprints, public_key)
|
|
23
|
-
raise ArgumentError,
|
|
23
|
+
raise ArgumentError, 'public_key is required' if public_key.nil? || public_key.empty?
|
|
24
24
|
|
|
25
25
|
key = new(public_key)
|
|
26
26
|
key if fingerprints.include?(key.fingerprint)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module Crypto
|
|
8
|
+
module SRP
|
|
9
|
+
SIZE_FOR_HASH = 256
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def compute_check(algo:, srp_b:, srp_id:, password:)
|
|
14
|
+
salt1 = algo[:salt1]
|
|
15
|
+
salt2 = algo[:salt2]
|
|
16
|
+
g = algo[:g]
|
|
17
|
+
p_bytes = algo[:p]
|
|
18
|
+
|
|
19
|
+
p_int = bytes_to_int(p_bytes)
|
|
20
|
+
g_int = g
|
|
21
|
+
b_int = bytes_to_int(srp_b)
|
|
22
|
+
|
|
23
|
+
pw_hash = compute_hash(salt1, salt2, password)
|
|
24
|
+
x = bytes_to_int(pw_hash)
|
|
25
|
+
|
|
26
|
+
p_for_hash = pad_to_256(p_bytes)
|
|
27
|
+
g_for_hash = int_to_256_bytes(g_int)
|
|
28
|
+
b_for_hash = pad_to_256(srp_b)
|
|
29
|
+
|
|
30
|
+
g_x = mod_pow(g_int, x, p_int)
|
|
31
|
+
k = bytes_to_int(sha256(p_for_hash, g_for_hash))
|
|
32
|
+
kg_x = (k * g_x) % p_int
|
|
33
|
+
|
|
34
|
+
a_int, a_for_hash, u = generate_and_check_random(g_int, p_int, b_for_hash)
|
|
35
|
+
|
|
36
|
+
g_b = (b_int - kg_x) % p_int
|
|
37
|
+
ux = u * x
|
|
38
|
+
a_ux = a_int + ux
|
|
39
|
+
s_int = mod_pow(g_b, a_ux, p_int)
|
|
40
|
+
k_key = sha256(int_to_256_bytes(s_int))
|
|
41
|
+
|
|
42
|
+
m1 = sha256(
|
|
43
|
+
xor(sha256(p_for_hash), sha256(g_for_hash)),
|
|
44
|
+
sha256(salt1),
|
|
45
|
+
sha256(salt2),
|
|
46
|
+
a_for_hash,
|
|
47
|
+
b_for_hash,
|
|
48
|
+
k_key
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
{ srp_id: srp_id, a: a_for_hash, m1: m1 }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def compute_hash(salt1, salt2, password)
|
|
55
|
+
hash1 = sha256(salt1, password.encode('utf-8'), salt1)
|
|
56
|
+
hash2 = sha256(salt2, hash1, salt2)
|
|
57
|
+
hash3 = pbkdf2_sha512(hash2, salt1, 100_000)
|
|
58
|
+
sha256(salt2, hash3, salt2)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def sha256(*parts)
|
|
62
|
+
digest = OpenSSL::Digest.new('SHA256')
|
|
63
|
+
parts.each { |p| digest.update(p) }
|
|
64
|
+
digest.digest
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def pbkdf2_sha512(password, salt, iterations)
|
|
68
|
+
OpenSSL::KDF.pbkdf2_hmac(
|
|
69
|
+
password,
|
|
70
|
+
salt: salt,
|
|
71
|
+
iterations: iterations,
|
|
72
|
+
length: 64,
|
|
73
|
+
hash: 'SHA512'
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def mod_pow(base, exp, mod)
|
|
78
|
+
base.to_bn.mod_exp(exp.to_bn, mod.to_bn).to_i
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def generate_and_check_random(g, p, b_for_hash)
|
|
82
|
+
loop do
|
|
83
|
+
random = SecureRandom.random_bytes(256)
|
|
84
|
+
a_int = bytes_to_int(random)
|
|
85
|
+
a_big = mod_pow(g, a_int, p)
|
|
86
|
+
a_for_hash = int_to_256_bytes(a_big)
|
|
87
|
+
u = bytes_to_int(sha256(a_for_hash, b_for_hash))
|
|
88
|
+
next if u.zero?
|
|
89
|
+
|
|
90
|
+
return [a_int, a_for_hash, u]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def pad_to_256(bytes)
|
|
95
|
+
bytes = bytes.b
|
|
96
|
+
return bytes if bytes.bytesize >= SIZE_FOR_HASH
|
|
97
|
+
|
|
98
|
+
("\x00" * (SIZE_FOR_HASH - bytes.bytesize)).b + bytes
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def int_to_256_bytes(num)
|
|
102
|
+
hex = num.to_s(16)
|
|
103
|
+
hex = "0#{hex}" if hex.length.odd?
|
|
104
|
+
raw = [hex].pack('H*')
|
|
105
|
+
pad_to_256(raw)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def bytes_to_int(bytes)
|
|
109
|
+
bytes.unpack1('H*').to_i(16)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def xor(a, b)
|
|
113
|
+
a.bytes.zip(b.bytes).map { |x, y| x ^ y }.pack('C*')
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DelegateMethods
|
|
4
|
+
def delegate(*methods, to:)
|
|
5
|
+
methods.each do |method|
|
|
6
|
+
define_method(method) do |*args, **kwargs, &block|
|
|
7
|
+
instance_variable_get(:"@#{to}").public_send(method, *args, **kwargs, &block)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|