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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/data/tl-schema.json +42686 -0
  3. data/ext/aes_ige/extconf.rb +3 -9
  4. data/ext/factorization/extconf.rb +2 -0
  5. data/lib/mtproto/auth_key_generator.rb +68 -105
  6. data/lib/mtproto/binary.rb +21 -0
  7. data/lib/mtproto/client/api/check_password.rb +41 -0
  8. data/lib/mtproto/client/api/export_login_token.rb +27 -0
  9. data/lib/mtproto/client/api/get_dialogs.rb +21 -0
  10. data/lib/mtproto/client/api/get_history.rb +20 -0
  11. data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
  12. data/lib/mtproto/client/api/get_updates_state.rb +14 -0
  13. data/lib/mtproto/client/api/get_users.rb +14 -0
  14. data/lib/mtproto/client/api/import_login_token.rb +23 -0
  15. data/lib/mtproto/client/api/send_code.rb +21 -0
  16. data/lib/mtproto/client/api/sign_in.rb +27 -0
  17. data/lib/mtproto/client/api.rb +36 -0
  18. data/lib/mtproto/client/rpc/response.rb +63 -0
  19. data/lib/mtproto/client/rpc.rb +60 -127
  20. data/lib/mtproto/client.rb +143 -32
  21. data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
  22. data/lib/mtproto/crypto/dh_validator.rb +17 -19
  23. data/lib/mtproto/crypto/factorization.rb +1 -1
  24. data/lib/mtproto/crypto/rsa_key.rb +2 -2
  25. data/lib/mtproto/crypto/srp.rb +117 -0
  26. data/lib/mtproto/delegate_methods.rb +11 -0
  27. data/lib/mtproto/errors.rb +8 -0
  28. data/lib/mtproto/message/message.rb +85 -0
  29. data/lib/mtproto/session.rb +1 -1
  30. data/lib/mtproto/tl/constructor_names.rb +2271 -0
  31. data/lib/mtproto/tl/constructors.rb +99 -0
  32. data/lib/mtproto/tl/object.rb +25 -0
  33. data/lib/mtproto/tl/objects/account_password.rb +69 -0
  34. data/lib/mtproto/tl/objects/authorization.rb +70 -0
  35. data/lib/mtproto/tl/objects/check_password.rb +43 -0
  36. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +45 -0
  37. data/lib/mtproto/tl/objects/dh_gen_response.rb +46 -0
  38. data/lib/mtproto/tl/objects/dialogs.rb +453 -0
  39. data/lib/mtproto/tl/objects/export_login_token.rb +48 -0
  40. data/lib/mtproto/tl/objects/get_config.rb +13 -0
  41. data/lib/mtproto/tl/objects/get_dialogs.rb +51 -0
  42. data/lib/mtproto/tl/objects/get_difference.rb +34 -0
  43. data/lib/mtproto/tl/objects/get_history.rb +49 -0
  44. data/lib/mtproto/tl/objects/get_password.rb +13 -0
  45. data/lib/mtproto/tl/objects/get_state.rb +13 -0
  46. data/lib/mtproto/tl/objects/get_users.rb +16 -0
  47. data/lib/mtproto/{type → tl/objects}/gzip_packed.rb +6 -6
  48. data/lib/mtproto/tl/objects/help_config.rb +76 -0
  49. data/lib/mtproto/tl/objects/import_login_token.rb +37 -0
  50. data/lib/mtproto/tl/objects/init_connection.rb +57 -0
  51. data/lib/mtproto/tl/objects/invoke_with_layer.rb +20 -0
  52. data/lib/mtproto/tl/objects/login_token.rb +78 -0
  53. data/lib/mtproto/{type → tl/objects}/message.rb +3 -3
  54. data/lib/mtproto/tl/objects/messages.rb +162 -0
  55. data/lib/mtproto/{type → tl/objects}/msg_container.rb +1 -3
  56. data/lib/mtproto/{type → tl/objects}/new_session_created.rb +1 -3
  57. data/lib/mtproto/tl/objects/pq_inner_data.rb +66 -0
  58. data/lib/mtproto/tl/objects/req_dh_params.rb +63 -0
  59. data/lib/mtproto/tl/objects/req_pq_multi.rb +21 -0
  60. data/lib/mtproto/tl/objects/res_pq.rb +73 -0
  61. data/lib/mtproto/{type → tl/objects}/rpc_error.rb +1 -4
  62. data/lib/mtproto/tl/objects/send_code.rb +47 -0
  63. data/lib/mtproto/tl/objects/sent_code.rb +79 -0
  64. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
  65. data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
  66. data/lib/mtproto/tl/objects/set_client_dh_params.rb +46 -0
  67. data/lib/mtproto/tl/objects/sign_in.rb +45 -0
  68. data/lib/mtproto/tl/objects/update.rb +77 -0
  69. data/lib/mtproto/tl/objects/update_short.rb +20 -0
  70. data/lib/mtproto/tl/objects/update_short_message.rb +65 -0
  71. data/lib/mtproto/tl/objects/updates_difference.rb +152 -0
  72. data/lib/mtproto/tl/objects/updates_state.rb +35 -0
  73. data/lib/mtproto/tl/objects/users.rb +83 -0
  74. data/lib/mtproto/tl/schema.rb +102 -0
  75. data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
  76. data/lib/mtproto/transport/connection.rb +23 -0
  77. data/lib/mtproto/transport/errors.rb +11 -0
  78. data/lib/mtproto/transport/packet.rb +19 -0
  79. data/lib/mtproto/transport/tcp_connection.rb +57 -46
  80. data/lib/mtproto/updates_poller.rb +37 -33
  81. data/lib/mtproto/version.rb +1 -1
  82. data/lib/mtproto.rb +17 -27
  83. data/scripts/generate_constructors.rb +65 -0
  84. metadata +76 -61
  85. data/lib/mtproto/async/middleware/base.rb +0 -17
  86. data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
  87. data/lib/mtproto/async/request.rb +0 -18
  88. data/lib/mtproto/async/request_queue.rb +0 -63
  89. data/lib/mtproto/async_client.rb +0 -201
  90. data/lib/mtproto/rpc/get_config.rb +0 -34
  91. data/lib/mtproto/rpc/get_contacts.rb +0 -29
  92. data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
  93. data/lib/mtproto/rpc/get_updates_state.rb +0 -29
  94. data/lib/mtproto/rpc/get_users.rb +0 -29
  95. data/lib/mtproto/rpc/ping.rb +0 -33
  96. data/lib/mtproto/rpc/send_code.rb +0 -41
  97. data/lib/mtproto/rpc/send_message.rb +0 -47
  98. data/lib/mtproto/rpc/sign_in.rb +0 -48
  99. data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
  100. data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
  101. data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
  102. data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
  103. data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
  104. data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
  105. data/lib/mtproto/type/bad_msg_notification.rb +0 -46
  106. data/lib/mtproto/type/client_dh_inner_data.rb +0 -29
  107. data/lib/mtproto/type/code_settings.rb +0 -25
  108. data/lib/mtproto/type/config.rb +0 -124
  109. data/lib/mtproto/type/pq_inner_data.rb +0 -41
  110. data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
  111. data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
  112. data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
  113. data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
  114. data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
  115. data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
  116. data/lib/mtproto/type/rpc/help/config.rb +0 -35
  117. data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
  118. data/lib/mtproto/type/rpc/init_connection.rb +0 -28
  119. data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
  120. data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
  121. data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
  122. data/lib/mtproto/type/rpc/ping.rb +0 -18
  123. data/lib/mtproto/type/rpc/pong.rb +0 -46
  124. data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
  125. data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
  126. data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
  127. data/lib/mtproto/type/rpc/updates/state.rb +0 -59
  128. data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
  129. data/lib/mtproto/type/rpc/users/users.rb +0 -99
  130. data/lib/mtproto/type/sent_code.rb +0 -128
  131. data/lib/mtproto/type/serializer.rb +0 -55
  132. 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 encode_packet(data)
10
- length = data.bytesize >> 2
9
+ def initialize(stream)
10
+ @stream = stream
11
+ end
12
+
13
+ def send(packet)
14
+ length = packet.size >> 2
11
15
 
12
- length_prefix = if length < 127
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
- length_prefix + data
22
+ @stream.write packet.data.pack('C*')
19
23
  end
20
24
 
21
- def decode_packet(data)
22
- length = data.unpack1('C')
23
- offset = 1
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
- length = (data[1, 3] + "\x00").unpack1('L<')
27
- offset = 4
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
- data[offset, actual_length]
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, :codec
9
+ attr_reader :host, :port
10
+ attr_accessor :wait_timeout, :read_timeout
12
11
 
13
- def initialize(host, port, codec)
12
+ def initialize(host, port, codec_class: AbridgedPacketCodec, wait_timeout: 60, read_timeout: 3)
14
13
  @host = host
15
14
  @port = port
16
- @codec = codec
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
- send_init_tag if @codec.class.const_defined?(:TAG)
28
+ write_init_tag
26
29
  end
27
30
 
28
- def connected?
29
- !@socket.nil? && !@socket.closed?
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 send(data)
33
- raise ConnectionError, 'Not connected' unless connected?
42
+ def not_connected?
43
+ @socket.nil? || @socket.closed?
44
+ end
34
45
 
35
- encoded = @codec.encode_packet(data)
36
- @socket.write(encoded)
46
+ def connected?
47
+ not not_connected?
37
48
  end
38
49
 
39
- def recv(timeout: 60)
40
- raise ConnectionError, 'Not connected' unless connected?
50
+ def send(packet)
51
+ raise NotConnectedError, 'Not connected' unless connected?
41
52
 
42
- Timeout.timeout(timeout) do
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 close
50
- return unless @socket
56
+ def receive(&)
57
+ raise NotConnectedError, 'Not connected' unless connected?
51
58
 
52
- @socket.close
53
- rescue StandardError
54
- nil
55
- ensure
56
- @socket = nil
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 send_init_tag
62
- tag = @codec.class.const_get(:TAG)
63
- @socket.write(tag) if tag
64
- end
68
+ def receive_once
69
+ return nil unless @socket.wait_readable(@wait_timeout)
65
70
 
66
- def read_packet
67
- first_byte = read_exactly(1)
68
- length = first_byte.unpack1('C')
71
+ read_packet
72
+ end
69
73
 
70
- if length >= 127
71
- length_bytes = read_exactly(3)
72
- length = (length_bytes + "\x00").unpack1('L<')
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
- actual_length = length << 2
76
- read_exactly(actual_length)
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 read_exactly(bytes_needed)
80
- result = ''.b
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
- result += chunk
86
- end
87
- result
97
+ tag = @codec_class.const_get(:TAG)
98
+ @socket.write tag if tag
88
99
  end
89
100
  end
90
101
  end