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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/Rakefile +13 -0
  4. data/lib/mtproto/auth_key_generator.rb +36 -13
  5. data/lib/mtproto/client/rpc.rb +141 -0
  6. data/lib/mtproto/client.rb +60 -185
  7. data/lib/mtproto/crypto/aes_ige.rb +1 -1
  8. data/lib/mtproto/crypto/factorization.rb +1 -1
  9. data/lib/mtproto/crypto/rsa_key.rb +9 -15
  10. data/lib/mtproto/errors.rb +33 -0
  11. data/lib/mtproto/message_id.rb +13 -0
  12. data/lib/mtproto/rpc/get_config.rb +37 -0
  13. data/lib/mtproto/rpc/get_contacts.rb +22 -0
  14. data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
  15. data/lib/mtproto/rpc/get_updates_state.rb +22 -0
  16. data/lib/mtproto/rpc/get_users.rb +22 -0
  17. data/lib/mtproto/rpc/ping.rb +26 -0
  18. data/lib/mtproto/rpc/send_code.rb +44 -0
  19. data/lib/mtproto/rpc/send_message.rb +31 -0
  20. data/lib/mtproto/rpc/sign_in.rb +52 -0
  21. data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
  22. data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
  23. data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
  24. data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
  25. data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
  26. data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
  27. data/lib/mtproto/tl/code_settings.rb +25 -0
  28. data/lib/mtproto/tl/config.rb +4 -2
  29. data/lib/mtproto/tl/gzip_packed.rb +1 -1
  30. data/lib/mtproto/tl/message.rb +8 -216
  31. data/lib/mtproto/tl/method_builder.rb +29 -0
  32. data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
  33. data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
  34. data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
  35. data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
  36. data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
  37. data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
  38. data/lib/mtproto/tl/rpc/help/config.rb +35 -0
  39. data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
  40. data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
  41. data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
  42. data/lib/mtproto/tl/rpc/ping.rb +18 -0
  43. data/lib/mtproto/tl/rpc/pong.rb +46 -0
  44. data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
  45. data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
  46. data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
  47. data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
  48. data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
  49. data/lib/mtproto/tl/rpc/users/users.rb +99 -0
  50. data/lib/mtproto/tl/sent_code.rb +128 -0
  51. data/lib/mtproto/transport/tcp_connection.rb +1 -1
  52. data/lib/mtproto/updates_poller.rb +111 -0
  53. data/lib/mtproto/version.rb +1 -1
  54. data/lib/mtproto.rb +13 -0
  55. metadata +57 -6
  56. data/ext/aes_ige/Makefile +0 -273
  57. data/ext/aes_ige/aes_ige.bundle +0 -0
  58. data/ext/factorization/Makefile +0 -273
  59. 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module MessageId
5
+ module_function
6
+
7
+ def generate(time_offset: 0)
8
+ time = Time.now.to_f + time_offset
9
+ msg_id = (time * (2**32)).to_i
10
+ (msg_id / 4) * 4
11
+ end
12
+ end
13
+ 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
@@ -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