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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UpdatesDifference
|
|
6
|
+
attr_reader :type, :date, :seq, :new_messages, :users, :pts, :state
|
|
7
|
+
|
|
8
|
+
def initialize(type:, date: nil, seq: nil, new_messages: [], users: [], pts: nil, state: nil)
|
|
9
|
+
@type = type
|
|
10
|
+
@date = date
|
|
11
|
+
@seq = seq
|
|
12
|
+
@new_messages = new_messages
|
|
13
|
+
@users = users
|
|
14
|
+
@pts = pts
|
|
15
|
+
@state = state
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.parse(data)
|
|
19
|
+
constructor = data[0, 4].unpack1('L<')
|
|
20
|
+
|
|
21
|
+
case constructor
|
|
22
|
+
when Constructors::UPDATES_DIFFERENCE_EMPTY
|
|
23
|
+
parse_empty(data)
|
|
24
|
+
when Constructors::UPDATES_DIFFERENCE, Constructors::UPDATES_DIFFERENCE_SLICE
|
|
25
|
+
parse_difference(data, constructor)
|
|
26
|
+
when Constructors::UPDATES_DIFFERENCE_TOO_LONG
|
|
27
|
+
new(type: :too_long, pts: data[4, 4].unpack1('L<'))
|
|
28
|
+
else
|
|
29
|
+
warn "Unknown Difference constructor: 0x#{constructor.to_s(16)}, treating as empty"
|
|
30
|
+
new(type: :empty, date: Time.now.to_i, seq: 0)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def parse_empty(data)
|
|
38
|
+
new(
|
|
39
|
+
type: :empty,
|
|
40
|
+
date: data[4, 4].unpack1('L<'),
|
|
41
|
+
seq: data[8, 4].unpack1('L<')
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_difference(data, constructor)
|
|
46
|
+
offset = 4
|
|
47
|
+
|
|
48
|
+
messages, offset = parse_messages_vector(data, offset)
|
|
49
|
+
offset = skip_vector(data, offset) # encrypted_messages
|
|
50
|
+
offset = skip_vector(data, offset) # other_updates
|
|
51
|
+
offset = skip_vector(data, offset) # chats
|
|
52
|
+
users, = parse_users_vector(data, offset)
|
|
53
|
+
|
|
54
|
+
new(
|
|
55
|
+
type: constructor == Constructors::UPDATES_DIFFERENCE ? :difference : :slice,
|
|
56
|
+
new_messages: messages,
|
|
57
|
+
users: users
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def parse_messages_vector(data, offset)
|
|
62
|
+
offset += 4 # vector constructor
|
|
63
|
+
count = data[offset, 4].unpack1('L<')
|
|
64
|
+
offset += 4
|
|
65
|
+
|
|
66
|
+
messages = []
|
|
67
|
+
count.times do
|
|
68
|
+
msg, offset = parse_message(data, offset)
|
|
69
|
+
messages << msg if msg
|
|
70
|
+
end
|
|
71
|
+
[messages, offset]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_message(data, offset)
|
|
75
|
+
msg_constructor = data[offset, 4].unpack1('L<')
|
|
76
|
+
offset += 4
|
|
77
|
+
|
|
78
|
+
return [nil, offset] unless msg_constructor == Constructors::MESSAGE
|
|
79
|
+
|
|
80
|
+
flags = data[offset, 4].unpack1('L<')
|
|
81
|
+
offset += 4
|
|
82
|
+
id = data[offset, 4].unpack1('L<')
|
|
83
|
+
offset += 4
|
|
84
|
+
offset += 12 if flags.anybits?(1 << 8) # from_id
|
|
85
|
+
offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
|
|
86
|
+
offset += 12 # peer_id
|
|
87
|
+
offset += 8 if flags.anybits?(1 << 11) # via_bot_id
|
|
88
|
+
date = data[offset, 4].unpack1('L<')
|
|
89
|
+
offset += 4
|
|
90
|
+
message_text, offset = read_tl_string(data, offset)
|
|
91
|
+
|
|
92
|
+
[{ id: id, date: date, message: message_text, flags: flags }, offset]
|
|
93
|
+
rescue StandardError
|
|
94
|
+
[nil, offset]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def parse_users_vector(data, offset)
|
|
98
|
+
offset += 4 # vector constructor
|
|
99
|
+
count = data[offset, 4].unpack1('L<')
|
|
100
|
+
offset += 4
|
|
101
|
+
|
|
102
|
+
users = []
|
|
103
|
+
count.times do
|
|
104
|
+
user_constructor = data[offset, 4].unpack1('L<')
|
|
105
|
+
offset += 4
|
|
106
|
+
next unless user_constructor == Constructors::USER
|
|
107
|
+
|
|
108
|
+
flags = data[offset, 4].unpack1('L<')
|
|
109
|
+
offset += 4
|
|
110
|
+
offset += 4 # flags2
|
|
111
|
+
user_id = data[offset, 8].unpack1('Q<')
|
|
112
|
+
offset += 8
|
|
113
|
+
|
|
114
|
+
access_hash = nil
|
|
115
|
+
if flags.anybits?(1 << 0)
|
|
116
|
+
access_hash = data[offset, 8].unpack1('Q<')
|
|
117
|
+
offset += 8
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
users << { id: user_id, access_hash: access_hash }
|
|
121
|
+
rescue StandardError
|
|
122
|
+
break
|
|
123
|
+
end
|
|
124
|
+
[users, offset]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def skip_vector(data, offset)
|
|
128
|
+
offset += 4 # vector constructor
|
|
129
|
+
count = data[offset, 4].unpack1('L<')
|
|
130
|
+
offset += 4
|
|
131
|
+
count.times { offset += 4 }
|
|
132
|
+
offset
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def read_tl_string(data, offset)
|
|
136
|
+
first_byte = data.getbyte(offset)
|
|
137
|
+
if first_byte == 254
|
|
138
|
+
len = data.getbyte(offset + 1) |
|
|
139
|
+
(data.getbyte(offset + 2) << 8) |
|
|
140
|
+
(data.getbyte(offset + 3) << 16)
|
|
141
|
+
total = 4 + len
|
|
142
|
+
else
|
|
143
|
+
len = first_byte
|
|
144
|
+
total = 1 + len
|
|
145
|
+
end
|
|
146
|
+
padding = (4 - (total % 4)) % 4
|
|
147
|
+
[data[offset + (total - len), len], offset + total + padding]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UpdatesState
|
|
6
|
+
attr_accessor :pts, :qts, :date, :seq, :unread_count
|
|
7
|
+
|
|
8
|
+
def initialize(pts:, qts:, date:, seq:, unread_count:)
|
|
9
|
+
@pts = pts
|
|
10
|
+
@qts = qts
|
|
11
|
+
@date = date
|
|
12
|
+
@seq = seq
|
|
13
|
+
@unread_count = unread_count
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.parse(data)
|
|
17
|
+
constructor = data[0, 4].unpack1('L<')
|
|
18
|
+
raise UnexpectedConstructorError, constructor unless constructor == Constructors::UPDATES_STATE
|
|
19
|
+
|
|
20
|
+
offset = 4
|
|
21
|
+
pts = data[offset, 4].unpack1('L<')
|
|
22
|
+
offset += 4
|
|
23
|
+
qts = data[offset, 4].unpack1('L<')
|
|
24
|
+
offset += 4
|
|
25
|
+
date = data[offset, 4].unpack1('L<')
|
|
26
|
+
offset += 4
|
|
27
|
+
seq = data[offset, 4].unpack1('L<')
|
|
28
|
+
offset += 4
|
|
29
|
+
unread_count = data[offset, 4].unpack1('L<')
|
|
30
|
+
|
|
31
|
+
new(pts: pts, qts: qts, date: date, seq: seq, unread_count: unread_count)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class Users
|
|
6
|
+
User = Struct.new(:id, :access_hash, :first_name, :last_name, :username, :phone, :flags, :flags2,
|
|
7
|
+
keyword_init: true)
|
|
8
|
+
|
|
9
|
+
def self.parse(data)
|
|
10
|
+
constructor = data[0, 4].unpack1('L<')
|
|
11
|
+
raise "Expected Vector constructor, got 0x#{constructor.to_s(16)}" unless constructor == Constructors::VECTOR
|
|
12
|
+
|
|
13
|
+
offset = 4
|
|
14
|
+
count = data[offset, 4].unpack1('L<')
|
|
15
|
+
offset += 4
|
|
16
|
+
|
|
17
|
+
users = []
|
|
18
|
+
count.times do
|
|
19
|
+
user_constructor = data[offset, 4].unpack1('L<')
|
|
20
|
+
offset += 4
|
|
21
|
+
next unless user_constructor == Constructors::USER
|
|
22
|
+
|
|
23
|
+
flags = data[offset, 4].unpack1('L<')
|
|
24
|
+
offset += 4
|
|
25
|
+
|
|
26
|
+
flags2 = data[offset, 4].unpack1('L<')
|
|
27
|
+
offset += 4
|
|
28
|
+
|
|
29
|
+
id = data[offset, 8].unpack1('Q<')
|
|
30
|
+
offset += 8
|
|
31
|
+
|
|
32
|
+
access_hash = nil
|
|
33
|
+
if flags.anybits?(1 << 0)
|
|
34
|
+
access_hash = data[offset, 8].unpack1('Q<')
|
|
35
|
+
offset += 8
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
first_name = nil
|
|
39
|
+
first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
|
|
40
|
+
|
|
41
|
+
last_name = nil
|
|
42
|
+
last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
|
|
43
|
+
|
|
44
|
+
username = nil
|
|
45
|
+
username, offset = read_tl_string(data, offset) if flags.anybits?(1 << 3)
|
|
46
|
+
|
|
47
|
+
phone = nil
|
|
48
|
+
phone, offset = read_tl_string(data, offset) if flags.anybits?(1 << 4)
|
|
49
|
+
|
|
50
|
+
users << User.new(
|
|
51
|
+
id: id, access_hash: access_hash,
|
|
52
|
+
first_name: first_name, last_name: last_name,
|
|
53
|
+
username: username, phone: phone,
|
|
54
|
+
flags: flags, flags2: flags2
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
break
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
users
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def read_tl_string(data, offset)
|
|
67
|
+
first_byte = data.getbyte(offset)
|
|
68
|
+
if first_byte == 254
|
|
69
|
+
len = data.getbyte(offset + 1) |
|
|
70
|
+
(data.getbyte(offset + 2) << 8) |
|
|
71
|
+
(data.getbyte(offset + 3) << 16)
|
|
72
|
+
total = 4 + len
|
|
73
|
+
else
|
|
74
|
+
len = first_byte
|
|
75
|
+
total = 1 + len
|
|
76
|
+
end
|
|
77
|
+
padding = (4 - (total % 4)) % 4
|
|
78
|
+
[data[offset + (total - len), len], offset + total + padding]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
class Schema
|
|
8
|
+
PRIMITIVES = {
|
|
9
|
+
'int' => 4, 'long' => 8, 'double' => 8,
|
|
10
|
+
'int128' => 16, 'int256' => 32
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(path)
|
|
14
|
+
raw = JSON.parse(File.read(path))
|
|
15
|
+
@constructors = {}
|
|
16
|
+
raw['constructors'].each do |c|
|
|
17
|
+
uid = c['id'].to_i & 0xFFFFFFFF
|
|
18
|
+
@constructors[uid] = c['params']
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def skip(data, offset)
|
|
23
|
+
constructor = data[offset, 4].unpack1('L<')
|
|
24
|
+
offset += 4
|
|
25
|
+
skip_params(data, offset, constructor)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def skip_vector(data, offset, &block)
|
|
29
|
+
offset += 4 # vector constructor 0x1cb5c415
|
|
30
|
+
count = data[offset, 4].unpack1('L<')
|
|
31
|
+
offset += 4
|
|
32
|
+
count.times { offset = block ? yield(data, offset) : skip(data, offset) }
|
|
33
|
+
offset
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def skip_params(data, offset, constructor)
|
|
37
|
+
params = @constructors[constructor]
|
|
38
|
+
raise "Unknown constructor: 0x#{constructor.to_s(16).rjust(8, '0')}" unless params
|
|
39
|
+
|
|
40
|
+
flag_values = {}
|
|
41
|
+
|
|
42
|
+
params.each do |param|
|
|
43
|
+
type = param['type']
|
|
44
|
+
|
|
45
|
+
if type == '#'
|
|
46
|
+
flag_values[param['name']] = data[offset, 4].unpack1('L<')
|
|
47
|
+
offset += 4
|
|
48
|
+
next
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if (m = type.match(/\A(\w+)\.(\d+)\?(.+)\z/))
|
|
52
|
+
flag_name = m[1]
|
|
53
|
+
bit = m[2].to_i
|
|
54
|
+
inner_type = m[3]
|
|
55
|
+
next if inner_type == 'true'
|
|
56
|
+
next unless flag_values[flag_name]&.anybits?(1 << bit)
|
|
57
|
+
|
|
58
|
+
offset = skip_type(data, offset, inner_type)
|
|
59
|
+
else
|
|
60
|
+
offset = skip_type(data, offset, type)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
offset
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def skip_type(data, offset, type)
|
|
69
|
+
if (size = PRIMITIVES[type])
|
|
70
|
+
return offset + size
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
case type
|
|
74
|
+
when 'string', 'bytes'
|
|
75
|
+
skip_tl_string(data, offset)
|
|
76
|
+
when 'Bool'
|
|
77
|
+
offset + 4
|
|
78
|
+
when /\AVector<(.+)>\z/
|
|
79
|
+
inner = Regexp.last_match(1)
|
|
80
|
+
skip_vector(data, offset) { |d, o| skip_type(d, o, inner) }
|
|
81
|
+
else
|
|
82
|
+
skip(data, offset)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def skip_tl_string(data, offset)
|
|
87
|
+
first_byte = data.getbyte(offset)
|
|
88
|
+
if first_byte == 254
|
|
89
|
+
len = data.getbyte(offset + 1) |
|
|
90
|
+
(data.getbyte(offset + 2) << 8) |
|
|
91
|
+
(data.getbyte(offset + 3) << 16)
|
|
92
|
+
total = 4 + len
|
|
93
|
+
else
|
|
94
|
+
len = first_byte
|
|
95
|
+
total = 1 + len
|
|
96
|
+
end
|
|
97
|
+
padding = (4 - (total % 4)) % 4
|
|
98
|
+
offset + total + padding
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -6,29 +6,52 @@ module MTProto
|
|
|
6
6
|
TAG = "\xef".b
|
|
7
7
|
OBFUSCATE_TAG = "\xef\xef\xef\xef".b
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
9
|
+
def initialize(stream)
|
|
10
|
+
@stream = stream
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def send(packet)
|
|
14
|
+
length = packet.size >> 2
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
[length].pack('C')
|
|
16
|
+
if length < 127
|
|
17
|
+
@stream.write [length].pack('C')
|
|
14
18
|
else
|
|
15
|
-
"\x7f".b + [length].pack('L<')[0, 3]
|
|
19
|
+
@stream.write "\x7f".b + [length].pack('L<')[0, 3]
|
|
16
20
|
end
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
@stream.write packet.data.pack('C*')
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
MAX_PACKET_SIZE = 1_048_576
|
|
26
|
+
|
|
27
|
+
def scan
|
|
28
|
+
first_byte = read_exactly(1)
|
|
29
|
+
length = first_byte.unpack1('C')
|
|
24
30
|
|
|
25
31
|
if length >= 127
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
length_bytes = read_exactly(3)
|
|
33
|
+
length = "#{length_bytes}\x00".unpack1('L<')
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
actual_length = length << 2
|
|
31
|
-
|
|
37
|
+
|
|
38
|
+
raise PacketReadError, "Packet too large: #{actual_length} bytes" if actual_length > MAX_PACKET_SIZE
|
|
39
|
+
|
|
40
|
+
data = read_exactly(actual_length)
|
|
41
|
+
Packet.new(data.bytes)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def read_exactly(bytes_needed)
|
|
47
|
+
result = ''.b
|
|
48
|
+
while result.bytesize < bytes_needed
|
|
49
|
+
chunk = @stream.read(bytes_needed - result.bytesize)
|
|
50
|
+
raise IOError, 'EOF while reading' if chunk.nil? || chunk.empty?
|
|
51
|
+
|
|
52
|
+
result += chunk
|
|
53
|
+
end
|
|
54
|
+
result
|
|
32
55
|
end
|
|
33
56
|
end
|
|
34
57
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Transport
|
|
5
|
+
class Connection
|
|
6
|
+
extend DelegateMethods
|
|
7
|
+
|
|
8
|
+
attr_reader :transport
|
|
9
|
+
|
|
10
|
+
def initialize(transport, wait_timeout: 60, read_timeout: 30)
|
|
11
|
+
@transport = transport
|
|
12
|
+
@transport.wait_timeout = wait_timeout
|
|
13
|
+
@transport.read_timeout = read_timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
delegate :connect!, :disconnect!, :connected?, :not_connected?,
|
|
17
|
+
:send, :receive,
|
|
18
|
+
:wait_timeout, :wait_timeout=,
|
|
19
|
+
:read_timeout, :read_timeout=,
|
|
20
|
+
to: :transport
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Transport
|
|
5
|
+
class ConnectionError < StandardError; end
|
|
6
|
+
class NotConnectedError < ConnectionError; end
|
|
7
|
+
class ConnectionClosedError < ConnectionError; end
|
|
8
|
+
class ReceiveTimeoutError < ConnectionError; end
|
|
9
|
+
class PacketReadError < ConnectionError; end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Transport
|
|
5
|
+
class Packet
|
|
6
|
+
attr_reader :data
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
raise ArgumentError, 'data must be an Array' unless data.is_a?(Array)
|
|
10
|
+
|
|
11
|
+
@data = data
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size
|
|
15
|
+
data.length
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -5,86 +5,97 @@ require 'timeout'
|
|
|
5
5
|
|
|
6
6
|
module MTProto
|
|
7
7
|
module Transport
|
|
8
|
-
class ConnectionError < StandardError; end
|
|
9
|
-
|
|
10
8
|
class TCPConnection
|
|
11
|
-
attr_reader :host, :port
|
|
9
|
+
attr_reader :host, :port
|
|
10
|
+
attr_accessor :wait_timeout, :read_timeout
|
|
12
11
|
|
|
13
|
-
def initialize(host, port,
|
|
12
|
+
def initialize(host, port, codec_class: AbridgedPacketCodec, wait_timeout: 60, read_timeout: 3)
|
|
14
13
|
@host = host
|
|
15
14
|
@port = port
|
|
16
|
-
@
|
|
15
|
+
@codec_class = codec_class
|
|
16
|
+
@wait_timeout = wait_timeout
|
|
17
|
+
@read_timeout = read_timeout
|
|
17
18
|
@socket = nil
|
|
19
|
+
@codec = nil
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def connect!
|
|
21
23
|
return if connected?
|
|
22
24
|
|
|
23
25
|
@socket = TCPSocket.new(@host, @port)
|
|
26
|
+
@codec = @codec_class.new(@socket)
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
write_init_tag
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
def
|
|
29
|
-
|
|
31
|
+
def disconnect!
|
|
32
|
+
return unless @socket
|
|
33
|
+
|
|
34
|
+
@socket.close
|
|
35
|
+
rescue StandardError
|
|
36
|
+
nil
|
|
37
|
+
ensure
|
|
38
|
+
@socket = nil
|
|
39
|
+
@codec = nil
|
|
30
40
|
end
|
|
31
41
|
|
|
32
|
-
def
|
|
33
|
-
|
|
42
|
+
def not_connected?
|
|
43
|
+
@socket.nil? || @socket.closed?
|
|
44
|
+
end
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
def connected?
|
|
47
|
+
not not_connected?
|
|
37
48
|
end
|
|
38
49
|
|
|
39
|
-
def
|
|
40
|
-
raise
|
|
50
|
+
def send(packet)
|
|
51
|
+
raise NotConnectedError, 'Not connected' unless connected?
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
read_packet
|
|
44
|
-
end
|
|
45
|
-
rescue Timeout::Error
|
|
46
|
-
raise ConnectionError, 'Receive timeout'
|
|
53
|
+
@codec.send(packet)
|
|
47
54
|
end
|
|
48
55
|
|
|
49
|
-
def
|
|
50
|
-
|
|
56
|
+
def receive(&)
|
|
57
|
+
raise NotConnectedError, 'Not connected' unless connected?
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
if block_given?
|
|
60
|
+
receive_loop(&)
|
|
61
|
+
else
|
|
62
|
+
receive_once
|
|
63
|
+
end
|
|
57
64
|
end
|
|
58
65
|
|
|
59
66
|
private
|
|
60
67
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
@socket.write(tag) if tag
|
|
64
|
-
end
|
|
68
|
+
def receive_once
|
|
69
|
+
return nil unless @socket.wait_readable(@wait_timeout)
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
length = first_byte.unpack1('C')
|
|
71
|
+
read_packet
|
|
72
|
+
end
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
def receive_loop
|
|
75
|
+
loop do
|
|
76
|
+
@socket.wait_readable
|
|
77
|
+
packet = read_packet
|
|
78
|
+
yield packet, nil
|
|
79
|
+
rescue ConnectionClosedError, NotConnectedError
|
|
80
|
+
raise
|
|
81
|
+
rescue PacketReadError => e
|
|
82
|
+
yield nil, e
|
|
73
83
|
end
|
|
84
|
+
end
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
def read_packet
|
|
87
|
+
Timeout.timeout(@read_timeout) { @codec.scan }
|
|
88
|
+
rescue Timeout::Error
|
|
89
|
+
raise PacketReadError, 'Packet read timeout'
|
|
90
|
+
rescue IOError, Errno::ECONNRESET => e
|
|
91
|
+
raise ConnectionClosedError, e.message
|
|
77
92
|
end
|
|
78
93
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
while result.bytesize < bytes_needed
|
|
82
|
-
chunk = @socket.read(bytes_needed - result.bytesize)
|
|
83
|
-
raise ConnectionError, 'EOF while reading' if chunk.nil? || chunk.empty?
|
|
94
|
+
def write_init_tag
|
|
95
|
+
return unless @codec_class.const_defined?(:TAG)
|
|
84
96
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
result
|
|
97
|
+
tag = @codec_class.const_get(:TAG)
|
|
98
|
+
@socket.write tag if tag
|
|
88
99
|
end
|
|
89
100
|
end
|
|
90
101
|
end
|