mtproto 0.0.5 → 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/.env.example +5 -0
- data/Rakefile +13 -0
- data/lib/mtproto/auth_key_generator.rb +36 -13
- data/lib/mtproto/client/rpc.rb +141 -0
- data/lib/mtproto/client.rb +60 -185
- data/lib/mtproto/crypto/aes_ige.rb +1 -1
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/crypto/rsa_key.rb +9 -15
- data/lib/mtproto/errors.rb +33 -0
- 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/code_settings.rb +25 -0
- data/lib/mtproto/tl/config.rb +4 -2
- data/lib/mtproto/tl/gzip_packed.rb +1 -1
- data/lib/mtproto/tl/message.rb +8 -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/tl/sent_code.rb +128 -0
- data/lib/mtproto/transport/tcp_connection.rb +1 -1
- data/lib/mtproto/updates_poller.rb +111 -0
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +13 -0
- metadata +57 -6
- data/ext/aes_ige/Makefile +0 -273
- data/ext/aes_ige/aes_ige.bundle +0 -0
- data/ext/factorization/Makefile +0 -273
- data/ext/factorization/factorization.bundle +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class NotConnectedError < Error
|
|
7
|
+
def initialize(msg = 'Not connected. Call connect first.')
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class PingMismatchError < Error
|
|
13
|
+
def initialize(msg = 'Ping ID mismatch')
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class RpcError < Error
|
|
19
|
+
attr_reader :error_code, :error_message
|
|
20
|
+
|
|
21
|
+
def initialize(error_code, error_message)
|
|
22
|
+
@error_code = error_code
|
|
23
|
+
@error_message = error_message
|
|
24
|
+
super("RPC Error #{error_code}: #{error_message}")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class UnexpectedConstructorError < Error
|
|
29
|
+
def initialize(constructor)
|
|
30
|
+
super("Unexpected constructor: 0x#{constructor.to_s(16)}")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/help/get_config'
|
|
4
|
+
require_relative '../tl/rpc/help/config'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class GetConfig
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
query = TL::RPC::Help::GetConfig.build
|
|
15
|
+
|
|
16
|
+
unless @client.instance_variable_get(:@connection_initialized)
|
|
17
|
+
query = @client.init_connection(
|
|
18
|
+
api_id: @client.api_id,
|
|
19
|
+
device_model: @client.device_model,
|
|
20
|
+
system_version: @client.system_version,
|
|
21
|
+
app_version: @client.app_version,
|
|
22
|
+
system_lang_code: @client.system_lang_code,
|
|
23
|
+
lang_pack: @client.lang_pack,
|
|
24
|
+
lang_code: @client.lang_code,
|
|
25
|
+
query: query
|
|
26
|
+
)
|
|
27
|
+
query = @client.invoke_with_layer(214, query)
|
|
28
|
+
@client.instance_variable_set(:@connection_initialized, true)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
response = @client.rpc.call(query)
|
|
32
|
+
|
|
33
|
+
TL::RPC::Help::Config.parse(response)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/contacts/get_contacts'
|
|
4
|
+
require_relative '../tl/rpc/contacts/contacts'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class GetContacts
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(hash: 0)
|
|
14
|
+
raise 'Auth key not generated' unless @client.auth_key
|
|
15
|
+
|
|
16
|
+
query = TL::RPC::Contacts::GetContacts.build(hash: hash)
|
|
17
|
+
response = @client.rpc.call(query)
|
|
18
|
+
TL::RPC::Contacts::Contacts.parse(response)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/updates/get_difference'
|
|
4
|
+
require_relative '../tl/rpc/updates/difference'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class GetUpdatesDifference
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(pts:, date:, qts:, pts_limit: 100, qts_limit: 100, pts_total_limit: nil)
|
|
14
|
+
raise 'Auth key not generated' unless @client.auth_key
|
|
15
|
+
raise ArgumentError, 'pts is required' if pts.nil?
|
|
16
|
+
raise ArgumentError, 'date is required' if date.nil?
|
|
17
|
+
raise ArgumentError, 'qts is required' if qts.nil?
|
|
18
|
+
|
|
19
|
+
query = TL::RPC::Updates::GetDifference.build(
|
|
20
|
+
pts: pts,
|
|
21
|
+
pts_limit: pts_limit,
|
|
22
|
+
pts_total_limit: pts_total_limit,
|
|
23
|
+
date: date,
|
|
24
|
+
qts: qts,
|
|
25
|
+
qts_limit: qts_limit
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
response = @client.rpc.call(query)
|
|
29
|
+
TL::RPC::Updates::Difference.parse(response)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/updates/get_state'
|
|
4
|
+
require_relative '../tl/rpc/updates/state'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class GetUpdatesState
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
raise 'Auth key not generated' unless @client.auth_key
|
|
15
|
+
|
|
16
|
+
query = TL::RPC::Updates::GetState.build
|
|
17
|
+
response = @client.rpc.call(query)
|
|
18
|
+
TL::RPC::Updates::State.parse(response)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../tl/rpc/users/get_users'
|
|
4
|
+
require_relative '../tl/rpc/users/users'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module RPC
|
|
8
|
+
class GetUsers
|
|
9
|
+
def initialize(client)
|
|
10
|
+
@client = client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
raise 'Auth key not generated' unless @client.auth_key
|
|
15
|
+
|
|
16
|
+
query = TL::RPC::Users::GetUsers.build
|
|
17
|
+
response = @client.rpc.call(query)
|
|
18
|
+
TL::RPC::Users::Users.parse(response)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class CodeSettings
|
|
6
|
+
CONSTRUCTOR = 0xad253d78
|
|
7
|
+
|
|
8
|
+
def self.serialize(settings = {})
|
|
9
|
+
body = Serializer.serialize_int(CONSTRUCTOR)
|
|
10
|
+
|
|
11
|
+
flags = 0
|
|
12
|
+
flags |= (1 << 0) if settings[:allow_flashcall]
|
|
13
|
+
flags |= (1 << 1) if settings[:current_number]
|
|
14
|
+
flags |= (1 << 4) if settings[:allow_app_hash]
|
|
15
|
+
flags |= (1 << 5) if settings[:allow_missed_call]
|
|
16
|
+
flags |= (1 << 7) if settings[:allow_firebase]
|
|
17
|
+
flags |= (1 << 9) if settings[:unknown_number]
|
|
18
|
+
|
|
19
|
+
body += Serializer.serialize_int(flags)
|
|
20
|
+
|
|
21
|
+
body
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/mtproto/tl/config.rb
CHANGED
|
@@ -10,6 +10,7 @@ module MTProto
|
|
|
10
10
|
|
|
11
11
|
def self.deserialize(data)
|
|
12
12
|
offset = 4
|
|
13
|
+
|
|
13
14
|
flags = data[offset, 4].unpack1('L<')
|
|
14
15
|
offset += 4
|
|
15
16
|
|
|
@@ -28,7 +29,7 @@ module MTProto
|
|
|
28
29
|
dc_options_constructor = data[offset, 4].unpack1('L<')
|
|
29
30
|
offset += 4
|
|
30
31
|
|
|
31
|
-
raise "Expected vector constructor" unless dc_options_constructor == 0x1cb5c415
|
|
32
|
+
raise "Expected vector constructor 0x1cb5c415, got 0x#{dc_options_constructor.to_s(16)}" unless dc_options_constructor == 0x1cb5c415
|
|
32
33
|
|
|
33
34
|
dc_options_count = data[offset, 4].unpack1('L<')
|
|
34
35
|
offset += 4
|
|
@@ -65,10 +66,11 @@ module MTProto
|
|
|
65
66
|
|
|
66
67
|
def self.deserialize_from(data)
|
|
67
68
|
offset = 0
|
|
69
|
+
|
|
68
70
|
constructor = data[offset, 4].unpack1('L<')
|
|
69
71
|
offset += 4
|
|
70
72
|
|
|
71
|
-
raise "Expected dcOption constructor" unless constructor == 0x18b7a10d
|
|
73
|
+
raise "Expected dcOption constructor 0x18b7a10d, got 0x#{constructor.to_s(16)}" unless constructor == 0x18b7a10d
|
|
72
74
|
|
|
73
75
|
flags = data[offset, 4].unpack1('L<')
|
|
74
76
|
offset += 4
|
|
@@ -34,7 +34,7 @@ module MTProto
|
|
|
34
34
|
compressed_data = data[offset, length]
|
|
35
35
|
raise "Not enough data: expected #{length}, got #{compressed_data&.bytesize}" if compressed_data.nil? || compressed_data.bytesize < length
|
|
36
36
|
|
|
37
|
-
Zlib::GzipReader.new(StringIO.new(compressed_data)).read
|
|
37
|
+
Zlib::GzipReader.new(StringIO.new(compressed_data)).read.force_encoding(Encoding::BINARY)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|