mtproto 0.0.3 → 0.0.5

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +13 -1
  3. data/ext/aes_ige/Makefile +273 -0
  4. data/ext/aes_ige/aes_ige.bundle +0 -0
  5. data/ext/aes_ige/aes_ige.c +103 -0
  6. data/ext/aes_ige/extconf.rb +25 -0
  7. data/ext/factorization/Makefile +273 -0
  8. data/ext/factorization/extconf.rb +3 -0
  9. data/ext/factorization/factorization.bundle +0 -0
  10. data/ext/factorization/factorization.c +62 -0
  11. data/lib/mtproto/auth_key_generator.rb +228 -0
  12. data/lib/mtproto/client.rb +235 -0
  13. data/lib/mtproto/crypto/aes_ige.rb +23 -0
  14. data/lib/mtproto/crypto/auth_key_helper.rb +25 -0
  15. data/lib/mtproto/crypto/dh_key_exchange.rb +44 -0
  16. data/lib/mtproto/crypto/dh_validator.rb +80 -0
  17. data/lib/mtproto/crypto/factorization.rb +39 -0
  18. data/lib/mtproto/crypto/message_key.rb +32 -0
  19. data/lib/mtproto/crypto/rsa_key.rb +66 -0
  20. data/lib/mtproto/crypto/rsa_pad.rb +59 -0
  21. data/lib/mtproto/encrypted_message.rb +86 -0
  22. data/lib/mtproto/session.rb +20 -0
  23. data/lib/mtproto/tl/bad_msg_notification.rb +46 -0
  24. data/lib/mtproto/tl/client_dh_inner_data.rb +29 -0
  25. data/lib/mtproto/tl/config.rb +122 -0
  26. data/lib/mtproto/tl/gzip_packed.rb +41 -0
  27. data/lib/mtproto/tl/message.rb +246 -0
  28. data/lib/mtproto/tl/msg_container.rb +40 -0
  29. data/lib/mtproto/tl/new_session_created.rb +30 -0
  30. data/lib/mtproto/tl/p_q_inner_data.rb +41 -0
  31. data/lib/mtproto/tl/rpc_error.rb +34 -0
  32. data/lib/mtproto/tl/serializer.rb +55 -0
  33. data/lib/mtproto/tl/server_dh_inner_data.rb +85 -0
  34. data/lib/mtproto/version.rb +1 -1
  35. data/lib/mtproto.rb +23 -0
  36. data/tmp/.keep +0 -0
  37. metadata +33 -1
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module MTProto
6
+ class Session
7
+ attr_reader :session_id, :seq_no
8
+
9
+ def initialize
10
+ @session_id = SecureRandom.random_bytes(8).unpack1('Q<')
11
+ @seq_no = 0
12
+ end
13
+
14
+ def next_seq_no(content_related: true)
15
+ current = @seq_no
16
+ @seq_no += content_related ? 1 : 0
17
+ current * 2 + (content_related ? 1 : 0)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class BadMsgNotification
6
+ CONSTRUCTOR = 0xa7eff811
7
+
8
+ attr_reader :bad_msg_id, :bad_msg_seqno, :error_code
9
+
10
+ def self.deserialize(data)
11
+ offset = 4
12
+ bad_msg_id = data[offset, 8].unpack1('Q<')
13
+ offset += 8
14
+
15
+ bad_msg_seqno = data[offset, 4].unpack1('L<')
16
+ offset += 4
17
+
18
+ error_code = data[offset, 4].unpack1('L<')
19
+
20
+ new(bad_msg_id: bad_msg_id, bad_msg_seqno: bad_msg_seqno, error_code: error_code)
21
+ end
22
+
23
+ def initialize(bad_msg_id:, bad_msg_seqno:, error_code:)
24
+ @bad_msg_id = bad_msg_id
25
+ @bad_msg_seqno = bad_msg_seqno
26
+ @error_code = error_code
27
+ end
28
+
29
+ def error_message
30
+ case @error_code
31
+ when 16 then "msg_id too low (client time is wrong)"
32
+ when 17 then "msg_id too high (client time needs sync)"
33
+ when 18 then "incorrect two lower order msg_id bits (must be divisible by 4)"
34
+ when 19 then "container msg_id is the same as previously received"
35
+ when 20 then "message too old"
36
+ when 32 then "msg_seqno too low"
37
+ when 33 then "msg_seqno too high"
38
+ when 34 then "even msg_seqno expected but odd received"
39
+ when 35 then "odd msg_seqno expected but even received"
40
+ when 48 then "incorrect server salt"
41
+ else "unknown error code #{@error_code}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'serializer'
4
+
5
+ module MTProto
6
+ module TL
7
+ class ClientDHInnerData
8
+ CONSTRUCTOR = 0x6643b654
9
+
10
+ attr_reader :nonce, :server_nonce, :retry_id, :g_b
11
+
12
+ def initialize(nonce:, server_nonce:, retry_id:, g_b:)
13
+ @nonce = nonce
14
+ @server_nonce = server_nonce
15
+ @retry_id = retry_id
16
+ @g_b = g_b
17
+ end
18
+
19
+ def serialize
20
+ data = Serializer.serialize_int(CONSTRUCTOR)
21
+ data += @nonce
22
+ data += @server_nonce
23
+ data += Serializer.serialize_long(@retry_id)
24
+ data += Serializer.serialize_bytes(@g_b)
25
+ data
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class Config
6
+ CONSTRUCTOR = 0xcc1a241e
7
+ CONSTRUCTOR_ALT = 0x3072cfa1
8
+
9
+ attr_reader :flags, :date, :expires, :test_mode, :this_dc, :dc_options
10
+
11
+ def self.deserialize(data)
12
+ offset = 4
13
+ flags = data[offset, 4].unpack1('L<')
14
+ offset += 4
15
+
16
+ date = data[offset, 4].unpack1('L<')
17
+ offset += 4
18
+
19
+ expires = data[offset, 4].unpack1('L<')
20
+ offset += 4
21
+
22
+ test_mode = (data[offset, 4].unpack1('L<') == 0x997275b5)
23
+ offset += 4
24
+
25
+ this_dc = data[offset, 4].unpack1('L<')
26
+ offset += 4
27
+
28
+ dc_options_constructor = data[offset, 4].unpack1('L<')
29
+ offset += 4
30
+
31
+ raise "Expected vector constructor" unless dc_options_constructor == 0x1cb5c415
32
+
33
+ dc_options_count = data[offset, 4].unpack1('L<')
34
+ offset += 4
35
+
36
+ dc_options = []
37
+ dc_options_count.times do
38
+ dc_option, bytes_read = DcOption.deserialize_from(data[offset..])
39
+ dc_options << dc_option
40
+ offset += bytes_read
41
+ end
42
+
43
+ new(
44
+ flags: flags,
45
+ date: date,
46
+ expires: expires,
47
+ test_mode: test_mode,
48
+ this_dc: this_dc,
49
+ dc_options: dc_options
50
+ )
51
+ end
52
+
53
+ def initialize(flags:, date:, expires:, test_mode:, this_dc:, dc_options:)
54
+ @flags = flags
55
+ @date = date
56
+ @expires = expires
57
+ @test_mode = test_mode
58
+ @this_dc = this_dc
59
+ @dc_options = dc_options
60
+ end
61
+ end
62
+
63
+ class DcOption
64
+ attr_reader :id, :ip_address, :port, :flags
65
+
66
+ def self.deserialize_from(data)
67
+ offset = 0
68
+ constructor = data[offset, 4].unpack1('L<')
69
+ offset += 4
70
+
71
+ raise "Expected dcOption constructor" unless constructor == 0x18b7a10d
72
+
73
+ flags = data[offset, 4].unpack1('L<')
74
+ offset += 4
75
+
76
+ id = data[offset, 4].unpack1('L<')
77
+ offset += 4
78
+
79
+ ip_length = data[offset].ord
80
+ offset += 1
81
+
82
+ ip_address = data[offset, ip_length]
83
+ offset += ip_length
84
+
85
+ padding = (4 - ((ip_length + 1) % 4)) % 4
86
+ offset += padding
87
+
88
+ port = data[offset, 4].unpack1('L<')
89
+ offset += 4
90
+
91
+ [new(id: id, ip_address: ip_address, port: port, flags: flags), offset]
92
+ end
93
+
94
+ def initialize(id:, ip_address:, port:, flags:)
95
+ @id = id
96
+ @ip_address = ip_address
97
+ @port = port
98
+ @flags = flags
99
+ end
100
+
101
+ def ipv6?
102
+ (@flags & 1) != 0
103
+ end
104
+
105
+ def media_only?
106
+ (@flags & 2) != 0
107
+ end
108
+
109
+ def tcpo_only?
110
+ (@flags & 4) != 0
111
+ end
112
+
113
+ def cdn?
114
+ (@flags & 8) != 0
115
+ end
116
+
117
+ def static?
118
+ (@flags & 16) != 0
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+ require 'stringio'
5
+
6
+ module MTProto
7
+ module TL
8
+ module GzipPacked
9
+ CONSTRUCTOR = 0x3072cfa1
10
+
11
+ module_function
12
+
13
+ def unpack(data)
14
+ offset = 4
15
+
16
+ length_byte = data[offset].ord
17
+ offset += 1
18
+ puts " [GZIP] Length byte: #{length_byte} (0x#{length_byte.to_s(16)})" if $DEBUG
19
+
20
+ if length_byte == 254
21
+ length_bytes = data[offset, 3]
22
+ length = (length_bytes + "\x00").unpack1('L<')
23
+ offset += 3
24
+ puts " [GZIP] Extended length: #{length} bytes" if $DEBUG
25
+ elsif length_byte == 255
26
+ raise "Invalid TL string length: 255"
27
+ else
28
+ length = length_byte
29
+ puts " [GZIP] Short length: #{length} bytes" if $DEBUG
30
+ end
31
+
32
+ raise "Invalid length: #{length.inspect}" unless length.is_a?(Integer) && length > 0
33
+
34
+ compressed_data = data[offset, length]
35
+ raise "Not enough data: expected #{length}, got #{compressed_data&.bytesize}" if compressed_data.nil? || compressed_data.bytesize < length
36
+
37
+ Zlib::GzipReader.new(StringIO.new(compressed_data)).read
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'serializer'
4
+
5
+ module MTProto
6
+ module TL
7
+ 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
+ attr_reader :auth_key_id, :msg_id, :body
22
+
23
+ def initialize(auth_key_id: 0, msg_id: nil, body: '')
24
+ @auth_key_id = auth_key_id
25
+ @msg_id = msg_id || generate_msg_id
26
+ @body = body
27
+ end
28
+
29
+ def serialize
30
+ auth_key_id_bytes = [@auth_key_id].pack('Q<')
31
+ msg_id_bytes = [@msg_id].pack('Q<')
32
+ body_length = [@body.bytesize].pack('L<')
33
+
34
+ auth_key_id_bytes + msg_id_bytes + body_length + @body
35
+ end
36
+
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
+ def self.deserialize(data)
77
+ auth_key_id = data[0, 8].unpack1('Q<')
78
+ msg_id = data[8, 8].unpack1('Q<')
79
+ body_length = data[16, 4].unpack1('L<')
80
+ body = data[20, body_length]
81
+
82
+ new(auth_key_id: auth_key_id, msg_id: msg_id, body: body)
83
+ end
84
+
85
+ def parse_res_pq
86
+ constructor = @body[0, 4].unpack1('L<')
87
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_RES_PQ
88
+
89
+ offset = 4
90
+
91
+ nonce = @body[offset, 16]
92
+ offset += 16
93
+
94
+ server_nonce = @body[offset, 16]
95
+ offset += 16
96
+
97
+ pq_length_byte = @body[offset].unpack1('C')
98
+ offset += 1
99
+
100
+ pq_length = if pq_length_byte == 254
101
+ @body[offset, 3].unpack1('L<') & 0xffffff
102
+ offset += 3
103
+ else
104
+ pq_length_byte
105
+ end
106
+
107
+ pq = @body[offset, pq_length]
108
+ offset += pq_length
109
+ offset += padding_length(pq_length + 1)
110
+
111
+ vector_constructor = @body[offset, 4].unpack1('L<')
112
+ offset += 4
113
+ raise 'Expected vector constructor' unless vector_constructor == 0x1cb5c415
114
+
115
+ fingerprints_count = @body[offset, 4].unpack1('L<')
116
+ offset += 4
117
+
118
+ fingerprints = []
119
+ fingerprints_count.times do
120
+ fingerprints << @body[offset, 8].unpack1('Q<')
121
+ offset += 8
122
+ end
123
+
124
+ {
125
+ nonce: nonce,
126
+ server_nonce: server_nonce,
127
+ pq: pq,
128
+ fingerprints: fingerprints
129
+ }
130
+ end
131
+
132
+ def parse_server_DH_params_ok
133
+ constructor = @body[0, 4].unpack1('L<')
134
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_SERVER_DH_PARAMS_OK
135
+
136
+ offset = 4
137
+
138
+ nonce = @body[offset, 16]
139
+ offset += 16
140
+
141
+ server_nonce = @body[offset, 16]
142
+ offset += 16
143
+
144
+ length_byte = @body[offset].ord
145
+ offset += 1
146
+
147
+ if length_byte == 254
148
+ length_bytes = @body[offset, 3].bytes
149
+ encrypted_answer_length = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16)
150
+ offset += 3
151
+ else
152
+ encrypted_answer_length = length_byte
153
+ end
154
+
155
+ encrypted_answer = @body[offset, encrypted_answer_length]
156
+
157
+ {
158
+ nonce: nonce,
159
+ server_nonce: server_nonce,
160
+ encrypted_answer: encrypted_answer
161
+ }
162
+ end
163
+
164
+ def parse_dh_gen_response
165
+ constructor = @body[0, 4].unpack1('L<')
166
+
167
+ offset = 4
168
+ nonce = @body[offset, 16]
169
+ offset += 16
170
+
171
+ server_nonce = @body[offset, 16]
172
+ offset += 16
173
+
174
+ new_nonce_hash = @body[offset, 16]
175
+
176
+ case constructor
177
+ when CONSTRUCTOR_DH_GEN_OK
178
+ { status: :ok, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
179
+ when CONSTRUCTOR_DH_GEN_RETRY
180
+ { status: :retry, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
181
+ when CONSTRUCTOR_DH_GEN_FAIL
182
+ { status: :fail, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
183
+ else
184
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}"
185
+ end
186
+ end
187
+
188
+ def self.ping(ping_id)
189
+ body = Serializer.serialize_int(CONSTRUCTOR_PING)
190
+ body += [ping_id].pack('Q<')
191
+
192
+ body
193
+ end
194
+
195
+ def parse_pong
196
+ constructor = @body[0, 4].unpack1('L<')
197
+
198
+ if constructor == CONSTRUCTOR_BAD_MSG_NOTIFICATION
199
+ bad_msg = TL::BadMsgNotification.deserialize(@body)
200
+ 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})"
201
+ end
202
+
203
+ if constructor == CONSTRUCTOR_MSG_CONTAINER
204
+ container = TL::MsgContainer.deserialize(@body)
205
+ pong_message = container.messages.find do |msg|
206
+ msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_PONG
207
+ end
208
+
209
+ raise 'No pong message found in container' unless pong_message
210
+
211
+ offset = 4
212
+ msg_id = pong_message[:body][offset, 8].unpack1('Q<')
213
+ offset += 8
214
+ ping_id = pong_message[:body][offset, 8].unpack1('Q<')
215
+
216
+ return { msg_id: msg_id, ping_id: ping_id }
217
+ end
218
+
219
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_PONG
220
+
221
+ offset = 4
222
+ msg_id = @body[offset, 8].unpack1('Q<')
223
+ offset += 8
224
+ ping_id = @body[offset, 8].unpack1('Q<')
225
+
226
+ { msg_id: msg_id, ping_id: ping_id }
227
+ end
228
+
229
+ private
230
+
231
+ def generate_msg_id
232
+ time = Time.now.to_f
233
+ msg_id = (time * (2**32)).to_i
234
+ (msg_id / 4) * 4
235
+ end
236
+
237
+ def padding_length(length)
238
+ (4 - (length % 4)) % 4
239
+ end
240
+
241
+ def self.padding_length(length)
242
+ (4 - (length % 4)) % 4
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class MsgContainer
6
+ CONSTRUCTOR = 0x73f1f8dc
7
+
8
+ attr_reader :messages
9
+
10
+ def self.deserialize(data)
11
+ offset = 4
12
+ message_count = data[offset, 4].unpack1('L<')
13
+ offset += 4
14
+
15
+ messages = []
16
+ message_count.times do
17
+ msg_id = data[offset, 8].unpack1('Q<')
18
+ offset += 8
19
+
20
+ seqno = data[offset, 4].unpack1('L<')
21
+ offset += 4
22
+
23
+ bytes = data[offset, 4].unpack1('L<')
24
+ offset += 4
25
+
26
+ body = data[offset, bytes]
27
+ offset += bytes
28
+
29
+ messages << { msg_id: msg_id, seqno: seqno, body: body }
30
+ end
31
+
32
+ new(messages: messages)
33
+ end
34
+
35
+ def initialize(messages:)
36
+ @messages = messages
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class NewSessionCreated
6
+ CONSTRUCTOR = 0x9ec20908
7
+
8
+ attr_reader :first_msg_id, :unique_id, :server_salt
9
+
10
+ def self.deserialize(data)
11
+ offset = 4
12
+ first_msg_id = data[offset, 8].unpack1('Q<')
13
+ offset += 8
14
+
15
+ unique_id = data[offset, 8].unpack1('Q<')
16
+ offset += 8
17
+
18
+ server_salt = data[offset, 8].unpack1('Q<')
19
+
20
+ new(first_msg_id: first_msg_id, unique_id: unique_id, server_salt: server_salt)
21
+ end
22
+
23
+ def initialize(first_msg_id:, unique_id:, server_salt:)
24
+ @first_msg_id = first_msg_id
25
+ @unique_id = unique_id
26
+ @server_salt = server_salt
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'serializer'
4
+
5
+ module MTProto
6
+ module TL
7
+ class PQInnerData
8
+ CONSTRUCTOR_DC = 0xa9f55f95
9
+ CONSTRUCTOR_TEMP_DC = 0x56fddf88
10
+
11
+ attr_reader :pq, :p, :q, :nonce, :server_nonce, :new_nonce, :dc, :expires_in
12
+
13
+ def initialize(pq:, p:, q:, nonce:, server_nonce:, new_nonce:, dc:, expires_in: nil)
14
+ @pq = pq
15
+ @p = p
16
+ @q = q
17
+ @nonce = nonce
18
+ @server_nonce = server_nonce
19
+ @new_nonce = new_nonce
20
+ @dc = dc
21
+ @expires_in = expires_in
22
+ end
23
+
24
+ def serialize
25
+ constructor = @expires_in ? CONSTRUCTOR_TEMP_DC : CONSTRUCTOR_DC
26
+
27
+ data = Serializer.serialize_int(constructor)
28
+ data += Serializer.serialize_bytes(Serializer.integer_to_bytes(@pq))
29
+ data += Serializer.serialize_bytes(Serializer.integer_to_bytes(@p))
30
+ data += Serializer.serialize_bytes(Serializer.integer_to_bytes(@q))
31
+ data += @nonce
32
+ data += @server_nonce
33
+ data += @new_nonce
34
+
35
+ data += Serializer.serialize_int(@expires_in) if @expires_in
36
+
37
+ data + Serializer.serialize_int(@dc)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class RpcError
6
+ CONSTRUCTOR = 0x2144ca19
7
+
8
+ attr_reader :error_code, :error_message
9
+
10
+ def self.deserialize(data)
11
+ offset = 4
12
+ error_code = data[offset, 4].unpack1('l<')
13
+ offset += 4
14
+
15
+ message_length = data[offset].ord
16
+ offset += 1
17
+
18
+ error_message = data[offset, message_length]
19
+ offset += message_length
20
+
21
+ new(error_code: error_code, error_message: error_message)
22
+ end
23
+
24
+ def initialize(error_code:, error_message:)
25
+ @error_code = error_code
26
+ @error_message = error_message
27
+ end
28
+
29
+ def to_s
30
+ "RPC Error #{@error_code}: #{@error_message}"
31
+ end
32
+ end
33
+ end
34
+ end