mtproto 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mtproto/auth_key_generator.rb +16 -6
- data/lib/mtproto/client/rpc.rb +141 -0
- data/lib/mtproto/client.rb +36 -181
- 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 +37 -0
- data/lib/mtproto/rpc/get_contacts.rb +22 -0
- data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
- data/lib/mtproto/rpc/get_updates_state.rb +22 -0
- data/lib/mtproto/rpc/get_users.rb +22 -0
- data/lib/mtproto/rpc/ping.rb +26 -0
- data/lib/mtproto/rpc/send_code.rb +44 -0
- data/lib/mtproto/rpc/send_message.rb +31 -0
- data/lib/mtproto/rpc/sign_in.rb +52 -0
- data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
- data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
- data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
- data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
- data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
- data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
- data/lib/mtproto/tl/message.rb +2 -216
- data/lib/mtproto/tl/method_builder.rb +29 -0
- data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
- data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
- data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
- data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
- data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
- data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
- data/lib/mtproto/tl/rpc/help/config.rb +35 -0
- data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
- data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
- data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
- data/lib/mtproto/tl/rpc/ping.rb +18 -0
- data/lib/mtproto/tl/rpc/pong.rb +46 -0
- data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
- data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
- data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
- data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
- data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
- data/lib/mtproto/tl/rpc/users/users.rb +99 -0
- data/lib/mtproto/updates_poller.rb +111 -0
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +10 -1
- metadata +53 -5
- data/ext/aes_ige/Makefile +0 -273
- data/ext/factorization/Makefile +0 -273
- data/lib/mtproto/connection.rb +0 -103
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/ping'
|
|
4
|
+
require_relative '../tl/rpc/pong'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class Ping
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(ping_id = nil)
|
|
14
|
+
ping_id ||= rand(2**63)
|
|
15
|
+
body = TL::RPC::Ping.build(ping_id)
|
|
16
|
+
|
|
17
|
+
response_body = @client.rpc.call(body)
|
|
18
|
+
pong = TL::RPC::Pong.parse(response_body)
|
|
19
|
+
|
|
20
|
+
raise PingMismatchError unless pong[:ping_id] == ping_id
|
|
21
|
+
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/auth/send_code'
|
|
4
|
+
require_relative '../tl/rpc/auth/sent_code'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class SendCode
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(phone_number, code_settings: {})
|
|
14
|
+
raise ArgumentError, 'phone_number is required' if phone_number.nil? || phone_number.empty?
|
|
15
|
+
|
|
16
|
+
query = TL::RPC::Auth::SendCode.build(
|
|
17
|
+
phone_number: phone_number,
|
|
18
|
+
api_id: @client.api_id,
|
|
19
|
+
api_hash: @client.api_hash,
|
|
20
|
+
code_settings: code_settings
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
unless @client.instance_variable_get(:@connection_initialized)
|
|
24
|
+
query = @client.init_connection(
|
|
25
|
+
api_id: @client.api_id,
|
|
26
|
+
device_model: @client.device_model,
|
|
27
|
+
system_version: @client.system_version,
|
|
28
|
+
app_version: @client.app_version,
|
|
29
|
+
system_lang_code: @client.system_lang_code,
|
|
30
|
+
lang_pack: @client.lang_pack,
|
|
31
|
+
lang_code: @client.lang_code,
|
|
32
|
+
query: query
|
|
33
|
+
)
|
|
34
|
+
query = @client.invoke_with_layer(214, query)
|
|
35
|
+
@client.instance_variable_set(:@connection_initialized, true)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
response = @client.rpc.call(query)
|
|
39
|
+
|
|
40
|
+
TL::RPC::Auth::SentCode.parse(response)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/messages/send_message'
|
|
4
|
+
require_relative '../tl/rpc/messages/updates'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class SendMessage
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(user_id:, access_hash:, message:, random_id: nil)
|
|
14
|
+
raise 'Auth key not generated' unless @client.auth_key
|
|
15
|
+
raise ArgumentError, 'user_id is required' if user_id.nil?
|
|
16
|
+
raise ArgumentError, 'access_hash is required' if access_hash.nil?
|
|
17
|
+
raise ArgumentError, 'message is required and cannot be empty' if message.nil? || message.empty?
|
|
18
|
+
|
|
19
|
+
query = TL::RPC::Messages::SendMessage.build(
|
|
20
|
+
user_id: user_id,
|
|
21
|
+
access_hash: access_hash,
|
|
22
|
+
message: message,
|
|
23
|
+
random_id: random_id
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
response = @client.rpc.call(query)
|
|
27
|
+
TL::RPC::Messages::Updates.parse(response)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/auth/sign_in'
|
|
4
|
+
require_relative '../tl/rpc/auth/authorization'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class SignIn
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(phone_number:, phone_code_hash:, phone_code:)
|
|
14
|
+
raise ArgumentError, 'phone_number is required' if phone_number.nil? || phone_number.empty?
|
|
15
|
+
raise ArgumentError, 'phone_code_hash is required' if phone_code_hash.nil? || phone_code_hash.empty?
|
|
16
|
+
raise ArgumentError, 'phone_code is required' if phone_code.nil? || phone_code.empty?
|
|
17
|
+
|
|
18
|
+
query = TL::RPC::Auth::SignIn.build(
|
|
19
|
+
phone_number: phone_number,
|
|
20
|
+
phone_code_hash: phone_code_hash,
|
|
21
|
+
phone_code: phone_code
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
unless @client.instance_variable_get(:@connection_initialized)
|
|
25
|
+
query = @client.init_connection(
|
|
26
|
+
api_id: @client.api_id,
|
|
27
|
+
device_model: @client.device_model,
|
|
28
|
+
system_version: @client.system_version,
|
|
29
|
+
app_version: @client.app_version,
|
|
30
|
+
system_lang_code: @client.system_lang_code,
|
|
31
|
+
lang_pack: @client.lang_pack,
|
|
32
|
+
lang_code: @client.lang_code,
|
|
33
|
+
query: query
|
|
34
|
+
)
|
|
35
|
+
query = @client.invoke_with_layer(214, query)
|
|
36
|
+
@client.instance_variable_set(:@connection_initialized, true)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
response = @client.rpc.call(query)
|
|
40
|
+
|
|
41
|
+
result = TL::RPC::Auth::Authorization.parse(response)
|
|
42
|
+
|
|
43
|
+
# Update client with user data if authorization was successful
|
|
44
|
+
if result[:authorization] && result[:user_id]
|
|
45
|
+
@client.update_user(user_id: result[:user_id], access_hash: result[:access_hash])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
module AuthKey
|
|
6
|
+
class DHGenResponse
|
|
7
|
+
CONSTRUCTOR_DH_GEN_OK = 0x3bcbf734
|
|
8
|
+
CONSTRUCTOR_DH_GEN_RETRY = 0x46dc1fb9
|
|
9
|
+
CONSTRUCTOR_DH_GEN_FAIL = 0xa69dae02
|
|
10
|
+
|
|
11
|
+
def self.parse(body)
|
|
12
|
+
constructor = body[0, 4].unpack1('L<')
|
|
13
|
+
|
|
14
|
+
offset = 4
|
|
15
|
+
nonce = body[offset, 16]
|
|
16
|
+
offset += 16
|
|
17
|
+
|
|
18
|
+
server_nonce = body[offset, 16]
|
|
19
|
+
offset += 16
|
|
20
|
+
|
|
21
|
+
new_nonce_hash = body[offset, 16]
|
|
22
|
+
|
|
23
|
+
case constructor
|
|
24
|
+
when CONSTRUCTOR_DH_GEN_OK
|
|
25
|
+
{ status: :ok, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
26
|
+
when CONSTRUCTOR_DH_GEN_RETRY
|
|
27
|
+
{ status: :retry, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
28
|
+
when CONSTRUCTOR_DH_GEN_FAIL
|
|
29
|
+
{ status: :fail, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
30
|
+
else
|
|
31
|
+
raise "Unexpected constructor: 0x#{constructor.to_s(16)}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
module AuthKey
|
|
8
|
+
class ReqDHParams
|
|
9
|
+
CONSTRUCTOR = 0xd712e4be
|
|
10
|
+
|
|
11
|
+
def self.build(nonce:, server_nonce:, p:, q:, public_key_fingerprint:, encrypted_data:)
|
|
12
|
+
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
13
|
+
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
14
|
+
|
|
15
|
+
p_bytes = Serializer.integer_to_bytes(p)
|
|
16
|
+
q_bytes = Serializer.integer_to_bytes(q)
|
|
17
|
+
|
|
18
|
+
body = Serializer.serialize_int(CONSTRUCTOR)
|
|
19
|
+
body += nonce
|
|
20
|
+
body += server_nonce
|
|
21
|
+
body += Serializer.serialize_bytes(p_bytes)
|
|
22
|
+
body += Serializer.serialize_bytes(q_bytes)
|
|
23
|
+
body += Serializer.serialize_long(public_key_fingerprint)
|
|
24
|
+
body += Serializer.serialize_bytes(encrypted_data)
|
|
25
|
+
|
|
26
|
+
body
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
module AuthKey
|
|
6
|
+
class ReqPqMulti
|
|
7
|
+
CONSTRUCTOR = 0xbe7e8ef1
|
|
8
|
+
|
|
9
|
+
def self.build(nonce)
|
|
10
|
+
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
11
|
+
|
|
12
|
+
constructor = [CONSTRUCTOR].pack('L<')
|
|
13
|
+
constructor + nonce
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
module AuthKey
|
|
6
|
+
class ResPq
|
|
7
|
+
CONSTRUCTOR = 0x05162463
|
|
8
|
+
|
|
9
|
+
def self.parse(body)
|
|
10
|
+
constructor = body[0, 4].unpack1('L<')
|
|
11
|
+
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
|
|
12
|
+
|
|
13
|
+
offset = 4
|
|
14
|
+
|
|
15
|
+
nonce = body[offset, 16]
|
|
16
|
+
offset += 16
|
|
17
|
+
|
|
18
|
+
server_nonce = body[offset, 16]
|
|
19
|
+
offset += 16
|
|
20
|
+
|
|
21
|
+
pq_length_byte = body[offset].unpack1('C')
|
|
22
|
+
offset += 1
|
|
23
|
+
|
|
24
|
+
pq_length = if pq_length_byte == 254
|
|
25
|
+
body[offset, 3].unpack1('L<') & 0xffffff
|
|
26
|
+
offset += 3
|
|
27
|
+
else
|
|
28
|
+
pq_length_byte
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
pq = body[offset, pq_length]
|
|
32
|
+
offset += pq_length
|
|
33
|
+
offset += padding_length(pq_length + 1)
|
|
34
|
+
|
|
35
|
+
vector_constructor = body[offset, 4].unpack1('L<')
|
|
36
|
+
offset += 4
|
|
37
|
+
raise 'Expected vector constructor' unless vector_constructor == 0x1cb5c415
|
|
38
|
+
|
|
39
|
+
fingerprints_count = body[offset, 4].unpack1('L<')
|
|
40
|
+
offset += 4
|
|
41
|
+
|
|
42
|
+
fingerprints = []
|
|
43
|
+
fingerprints_count.times do
|
|
44
|
+
fingerprints << body[offset, 8].unpack1('Q<')
|
|
45
|
+
offset += 8
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
nonce: nonce,
|
|
50
|
+
server_nonce: server_nonce,
|
|
51
|
+
pq: pq,
|
|
52
|
+
fingerprints: fingerprints
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.padding_length(length)
|
|
57
|
+
(4 - (length % 4)) % 4
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
module AuthKey
|
|
6
|
+
class ServerDHParams
|
|
7
|
+
CONSTRUCTOR = 0xd0e8075c
|
|
8
|
+
|
|
9
|
+
def self.parse(body)
|
|
10
|
+
constructor = body[0, 4].unpack1('L<')
|
|
11
|
+
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
|
|
12
|
+
|
|
13
|
+
offset = 4
|
|
14
|
+
|
|
15
|
+
nonce = body[offset, 16]
|
|
16
|
+
offset += 16
|
|
17
|
+
|
|
18
|
+
server_nonce = body[offset, 16]
|
|
19
|
+
offset += 16
|
|
20
|
+
|
|
21
|
+
length_byte = body[offset].ord
|
|
22
|
+
offset += 1
|
|
23
|
+
|
|
24
|
+
if length_byte == 254
|
|
25
|
+
length_bytes = body[offset, 3].bytes
|
|
26
|
+
encrypted_answer_length = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16)
|
|
27
|
+
offset += 3
|
|
28
|
+
else
|
|
29
|
+
encrypted_answer_length = length_byte
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
encrypted_answer = body[offset, encrypted_answer_length]
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
nonce: nonce,
|
|
36
|
+
server_nonce: server_nonce,
|
|
37
|
+
encrypted_answer: encrypted_answer
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
module AuthKey
|
|
8
|
+
class SetClientDHParams
|
|
9
|
+
CONSTRUCTOR = 0xf5045f1f
|
|
10
|
+
|
|
11
|
+
def self.build(nonce:, server_nonce:, encrypted_data:)
|
|
12
|
+
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
13
|
+
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
14
|
+
|
|
15
|
+
body = Serializer.serialize_int(CONSTRUCTOR)
|
|
16
|
+
body += nonce
|
|
17
|
+
body += server_nonce
|
|
18
|
+
body += Serializer.serialize_bytes(encrypted_data)
|
|
19
|
+
|
|
20
|
+
body
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/mtproto/tl/message.rb
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'serializer'
|
|
4
|
-
|
|
5
3
|
module MTProto
|
|
6
4
|
module TL
|
|
7
5
|
class Message
|
|
8
|
-
CONSTRUCTOR_REQ_PQ_MULTI = 0xbe7e8ef1
|
|
9
|
-
CONSTRUCTOR_RES_PQ = 0x05162463
|
|
10
|
-
CONSTRUCTOR_REQ_DH_PARAMS = 0xd712e4be
|
|
11
|
-
CONSTRUCTOR_SERVER_DH_PARAMS_OK = 0xd0e8075c
|
|
12
|
-
CONSTRUCTOR_SET_CLIENT_DH_PARAMS = 0xf5045f1f
|
|
13
|
-
CONSTRUCTOR_DH_GEN_OK = 0x3bcbf734
|
|
14
|
-
CONSTRUCTOR_DH_GEN_RETRY = 0x46dc1fb9
|
|
15
|
-
CONSTRUCTOR_DH_GEN_FAIL = 0xa69dae02
|
|
16
|
-
CONSTRUCTOR_PING = 0x7abe77ec
|
|
17
|
-
CONSTRUCTOR_PONG = 0x347773c5
|
|
18
|
-
CONSTRUCTOR_BAD_MSG_NOTIFICATION = 0xa7eff811
|
|
19
|
-
CONSTRUCTOR_MSG_CONTAINER = 0x73f1f8dc
|
|
20
|
-
|
|
21
6
|
attr_reader :auth_key_id, :msg_id, :body
|
|
22
7
|
|
|
23
|
-
def initialize(auth_key_id
|
|
8
|
+
def initialize(auth_key_id:, msg_id:, body:)
|
|
24
9
|
@auth_key_id = auth_key_id
|
|
25
|
-
@msg_id = msg_id
|
|
10
|
+
@msg_id = msg_id
|
|
26
11
|
@body = body
|
|
27
12
|
end
|
|
28
13
|
|
|
@@ -34,45 +19,6 @@ module MTProto
|
|
|
34
19
|
auth_key_id_bytes + msg_id_bytes + body_length + @body
|
|
35
20
|
end
|
|
36
21
|
|
|
37
|
-
def self.req_pq_multi(nonce)
|
|
38
|
-
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
39
|
-
|
|
40
|
-
constructor = [CONSTRUCTOR_REQ_PQ_MULTI].pack('L<')
|
|
41
|
-
body = constructor + nonce
|
|
42
|
-
|
|
43
|
-
new(auth_key_id: 0, body: body)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def self.req_DH_params(nonce:, server_nonce:, p:, q:, public_key_fingerprint:, encrypted_data:)
|
|
47
|
-
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
48
|
-
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
49
|
-
|
|
50
|
-
p_bytes = Serializer.integer_to_bytes(p)
|
|
51
|
-
q_bytes = Serializer.integer_to_bytes(q)
|
|
52
|
-
|
|
53
|
-
body = Serializer.serialize_int(CONSTRUCTOR_REQ_DH_PARAMS)
|
|
54
|
-
body += nonce
|
|
55
|
-
body += server_nonce
|
|
56
|
-
body += Serializer.serialize_bytes(p_bytes)
|
|
57
|
-
body += Serializer.serialize_bytes(q_bytes)
|
|
58
|
-
body += Serializer.serialize_long(public_key_fingerprint)
|
|
59
|
-
body += Serializer.serialize_bytes(encrypted_data)
|
|
60
|
-
|
|
61
|
-
new(auth_key_id: 0, body: body)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def self.set_client_DH_params(nonce:, server_nonce:, encrypted_data:)
|
|
65
|
-
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
66
|
-
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
67
|
-
|
|
68
|
-
body = Serializer.serialize_int(CONSTRUCTOR_SET_CLIENT_DH_PARAMS)
|
|
69
|
-
body += nonce
|
|
70
|
-
body += server_nonce
|
|
71
|
-
body += Serializer.serialize_bytes(encrypted_data)
|
|
72
|
-
|
|
73
|
-
new(auth_key_id: 0, body: body)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
22
|
def self.deserialize(data)
|
|
77
23
|
if data.bytesize < 20
|
|
78
24
|
raise(ArgumentError,
|
|
@@ -87,166 +33,6 @@ module MTProto
|
|
|
87
33
|
|
|
88
34
|
new(auth_key_id: auth_key_id, msg_id: msg_id, body: body)
|
|
89
35
|
end
|
|
90
|
-
|
|
91
|
-
def parse_res_pq
|
|
92
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
93
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_RES_PQ
|
|
94
|
-
|
|
95
|
-
offset = 4
|
|
96
|
-
|
|
97
|
-
nonce = @body[offset, 16]
|
|
98
|
-
offset += 16
|
|
99
|
-
|
|
100
|
-
server_nonce = @body[offset, 16]
|
|
101
|
-
offset += 16
|
|
102
|
-
|
|
103
|
-
pq_length_byte = @body[offset].unpack1('C')
|
|
104
|
-
offset += 1
|
|
105
|
-
|
|
106
|
-
pq_length = if pq_length_byte == 254
|
|
107
|
-
@body[offset, 3].unpack1('L<') & 0xffffff
|
|
108
|
-
offset += 3
|
|
109
|
-
else
|
|
110
|
-
pq_length_byte
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
pq = @body[offset, pq_length]
|
|
114
|
-
offset += pq_length
|
|
115
|
-
offset += padding_length(pq_length + 1)
|
|
116
|
-
|
|
117
|
-
vector_constructor = @body[offset, 4].unpack1('L<')
|
|
118
|
-
offset += 4
|
|
119
|
-
raise 'Expected vector constructor' unless vector_constructor == 0x1cb5c415
|
|
120
|
-
|
|
121
|
-
fingerprints_count = @body[offset, 4].unpack1('L<')
|
|
122
|
-
offset += 4
|
|
123
|
-
|
|
124
|
-
fingerprints = []
|
|
125
|
-
fingerprints_count.times do
|
|
126
|
-
fingerprints << @body[offset, 8].unpack1('Q<')
|
|
127
|
-
offset += 8
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
nonce: nonce,
|
|
132
|
-
server_nonce: server_nonce,
|
|
133
|
-
pq: pq,
|
|
134
|
-
fingerprints: fingerprints
|
|
135
|
-
}
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def parse_server_DH_params_ok
|
|
139
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
140
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_SERVER_DH_PARAMS_OK
|
|
141
|
-
|
|
142
|
-
offset = 4
|
|
143
|
-
|
|
144
|
-
nonce = @body[offset, 16]
|
|
145
|
-
offset += 16
|
|
146
|
-
|
|
147
|
-
server_nonce = @body[offset, 16]
|
|
148
|
-
offset += 16
|
|
149
|
-
|
|
150
|
-
length_byte = @body[offset].ord
|
|
151
|
-
offset += 1
|
|
152
|
-
|
|
153
|
-
if length_byte == 254
|
|
154
|
-
length_bytes = @body[offset, 3].bytes
|
|
155
|
-
encrypted_answer_length = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16)
|
|
156
|
-
offset += 3
|
|
157
|
-
else
|
|
158
|
-
encrypted_answer_length = length_byte
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
encrypted_answer = @body[offset, encrypted_answer_length]
|
|
162
|
-
|
|
163
|
-
{
|
|
164
|
-
nonce: nonce,
|
|
165
|
-
server_nonce: server_nonce,
|
|
166
|
-
encrypted_answer: encrypted_answer
|
|
167
|
-
}
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def parse_dh_gen_response
|
|
171
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
172
|
-
|
|
173
|
-
offset = 4
|
|
174
|
-
nonce = @body[offset, 16]
|
|
175
|
-
offset += 16
|
|
176
|
-
|
|
177
|
-
server_nonce = @body[offset, 16]
|
|
178
|
-
offset += 16
|
|
179
|
-
|
|
180
|
-
new_nonce_hash = @body[offset, 16]
|
|
181
|
-
|
|
182
|
-
case constructor
|
|
183
|
-
when CONSTRUCTOR_DH_GEN_OK
|
|
184
|
-
{ status: :ok, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
185
|
-
when CONSTRUCTOR_DH_GEN_RETRY
|
|
186
|
-
{ status: :retry, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
187
|
-
when CONSTRUCTOR_DH_GEN_FAIL
|
|
188
|
-
{ status: :fail, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
189
|
-
else
|
|
190
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}"
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def self.ping(ping_id)
|
|
195
|
-
body = Serializer.serialize_int(CONSTRUCTOR_PING)
|
|
196
|
-
body += [ping_id].pack('Q<')
|
|
197
|
-
|
|
198
|
-
body
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def parse_pong
|
|
202
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
203
|
-
|
|
204
|
-
if constructor == CONSTRUCTOR_BAD_MSG_NOTIFICATION
|
|
205
|
-
bad_msg = TL::BadMsgNotification.deserialize(@body)
|
|
206
|
-
raise "Bad message notification: #{bad_msg.error_message} (code: #{bad_msg.error_code}, msg_id: #{bad_msg.bad_msg_id}, seqno: #{bad_msg.bad_msg_seqno})"
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
if constructor == CONSTRUCTOR_MSG_CONTAINER
|
|
210
|
-
container = TL::MsgContainer.deserialize(@body)
|
|
211
|
-
pong_message = container.messages.find do |msg|
|
|
212
|
-
msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_PONG
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
raise 'No pong message found in container' unless pong_message
|
|
216
|
-
|
|
217
|
-
offset = 4
|
|
218
|
-
msg_id = pong_message[:body][offset, 8].unpack1('Q<')
|
|
219
|
-
offset += 8
|
|
220
|
-
ping_id = pong_message[:body][offset, 8].unpack1('Q<')
|
|
221
|
-
|
|
222
|
-
return { msg_id: msg_id, ping_id: ping_id }
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_PONG
|
|
226
|
-
|
|
227
|
-
offset = 4
|
|
228
|
-
msg_id = @body[offset, 8].unpack1('Q<')
|
|
229
|
-
offset += 8
|
|
230
|
-
ping_id = @body[offset, 8].unpack1('Q<')
|
|
231
|
-
|
|
232
|
-
{ msg_id: msg_id, ping_id: ping_id }
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
private
|
|
236
|
-
|
|
237
|
-
def generate_msg_id
|
|
238
|
-
time = Time.now.to_f
|
|
239
|
-
msg_id = (time * (2**32)).to_i
|
|
240
|
-
(msg_id / 4) * 4
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def padding_length(length)
|
|
244
|
-
(4 - (length % 4)) % 4
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def self.padding_length(length)
|
|
248
|
-
(4 - (length % 4)) % 4
|
|
249
|
-
end
|
|
250
36
|
end
|
|
251
37
|
end
|
|
252
38
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
module MethodBuilder
|
|
8
|
+
def invoke_with_layer(layer, query)
|
|
9
|
+
body = Serializer.serialize_int(0xda9b0d0d)
|
|
10
|
+
body += Serializer.serialize_int(layer)
|
|
11
|
+
body + query
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def init_connection(api_id:, device_model:, system_version:, app_version:, system_lang_code:, lang_pack:, lang_code:, query:)
|
|
15
|
+
body = Serializer.serialize_int(0xc1cd5ea9)
|
|
16
|
+
flags = 0
|
|
17
|
+
body += Serializer.serialize_int(flags)
|
|
18
|
+
body += Serializer.serialize_int(api_id)
|
|
19
|
+
body += Serializer.serialize_string(device_model)
|
|
20
|
+
body += Serializer.serialize_string(system_version)
|
|
21
|
+
body += Serializer.serialize_string(app_version)
|
|
22
|
+
body += Serializer.serialize_string(system_lang_code)
|
|
23
|
+
body += Serializer.serialize_string(lang_pack)
|
|
24
|
+
body += Serializer.serialize_string(lang_code)
|
|
25
|
+
body + query
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|