mtproto 0.0.8 → 0.0.9

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/ext/aes_ige/extconf.rb +3 -9
  3. data/ext/factorization/extconf.rb +2 -0
  4. data/lib/mtproto/auth_key_generator.rb +68 -105
  5. data/lib/mtproto/binary.rb +21 -0
  6. data/lib/mtproto/client/api/check_password.rb +41 -0
  7. data/lib/mtproto/client/api/export_login_token.rb +27 -0
  8. data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
  9. data/lib/mtproto/client/api/get_updates_state.rb +14 -0
  10. data/lib/mtproto/client/api/get_users.rb +14 -0
  11. data/lib/mtproto/client/api/import_login_token.rb +23 -0
  12. data/lib/mtproto/client/api/send_code.rb +21 -0
  13. data/lib/mtproto/client/api/sign_in.rb +27 -0
  14. data/lib/mtproto/client/api.rb +28 -0
  15. data/lib/mtproto/client/rpc/response.rb +63 -0
  16. data/lib/mtproto/client/rpc.rb +61 -125
  17. data/lib/mtproto/client.rb +142 -31
  18. data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
  19. data/lib/mtproto/crypto/dh_validator.rb +17 -19
  20. data/lib/mtproto/crypto/factorization.rb +1 -1
  21. data/lib/mtproto/crypto/rsa_key.rb +2 -2
  22. data/lib/mtproto/crypto/srp.rb +117 -0
  23. data/lib/mtproto/delegate_methods.rb +11 -0
  24. data/lib/mtproto/message/message.rb +85 -0
  25. data/lib/mtproto/session.rb +1 -1
  26. data/lib/mtproto/tl/constructors.rb +2269 -0
  27. data/lib/mtproto/tl/object.rb +25 -0
  28. data/lib/mtproto/tl/objects/account_password.rb +72 -0
  29. data/lib/mtproto/tl/objects/authorization.rb +73 -0
  30. data/lib/mtproto/tl/objects/check_password.rb +46 -0
  31. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +47 -0
  32. data/lib/mtproto/tl/objects/dh_gen_response.rb +50 -0
  33. data/lib/mtproto/tl/objects/export_login_token.rb +51 -0
  34. data/lib/mtproto/tl/objects/get_config.rb +15 -0
  35. data/lib/mtproto/tl/objects/get_difference.rb +36 -0
  36. data/lib/mtproto/tl/objects/get_password.rb +15 -0
  37. data/lib/mtproto/tl/objects/get_state.rb +15 -0
  38. data/lib/mtproto/tl/objects/get_users.rb +20 -0
  39. data/lib/mtproto/tl/objects/help_config.rb +77 -0
  40. data/lib/mtproto/tl/objects/import_login_token.rb +39 -0
  41. data/lib/mtproto/tl/objects/init_connection.rb +59 -0
  42. data/lib/mtproto/tl/objects/invoke_with_layer.rb +22 -0
  43. data/lib/mtproto/tl/objects/login_token.rb +82 -0
  44. data/lib/mtproto/tl/objects/pq_inner_data.rb +69 -0
  45. data/lib/mtproto/tl/objects/req_dh_params.rb +65 -0
  46. data/lib/mtproto/tl/objects/req_pq_multi.rb +23 -0
  47. data/lib/mtproto/tl/objects/res_pq.rb +75 -0
  48. data/lib/mtproto/tl/objects/send_code.rb +50 -0
  49. data/lib/mtproto/tl/objects/sent_code.rb +79 -0
  50. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
  51. data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
  52. data/lib/mtproto/tl/objects/set_client_dh_params.rb +48 -0
  53. data/lib/mtproto/tl/objects/sign_in.rb +47 -0
  54. data/lib/mtproto/tl/objects/update.rb +80 -0
  55. data/lib/mtproto/tl/objects/update_short.rb +22 -0
  56. data/lib/mtproto/tl/objects/update_short_message.rb +67 -0
  57. data/lib/mtproto/tl/objects/updates_difference.rb +157 -0
  58. data/lib/mtproto/tl/objects/updates_state.rb +37 -0
  59. data/lib/mtproto/tl/objects/users.rb +86 -0
  60. data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
  61. data/lib/mtproto/transport/connection.rb +23 -0
  62. data/lib/mtproto/transport/errors.rb +11 -0
  63. data/lib/mtproto/transport/packet.rb +19 -0
  64. data/lib/mtproto/transport/tcp_connection.rb +57 -46
  65. data/lib/mtproto/type/bad_msg_notification.rb +10 -10
  66. data/lib/mtproto/type/gzip_packed.rb +5 -3
  67. data/lib/mtproto/type/message.rb +2 -2
  68. data/lib/mtproto/type/rpc_error.rb +0 -1
  69. data/lib/mtproto/updates_poller.rb +37 -33
  70. data/lib/mtproto/version.rb +1 -1
  71. data/lib/mtproto.rb +11 -17
  72. data/scripts/generate_constructors.rb +65 -0
  73. metadata +62 -51
  74. data/lib/mtproto/async/middleware/base.rb +0 -17
  75. data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
  76. data/lib/mtproto/async/request.rb +0 -18
  77. data/lib/mtproto/async/request_queue.rb +0 -63
  78. data/lib/mtproto/async_client.rb +0 -201
  79. data/lib/mtproto/rpc/get_config.rb +0 -34
  80. data/lib/mtproto/rpc/get_contacts.rb +0 -29
  81. data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
  82. data/lib/mtproto/rpc/get_updates_state.rb +0 -29
  83. data/lib/mtproto/rpc/get_users.rb +0 -29
  84. data/lib/mtproto/rpc/ping.rb +0 -33
  85. data/lib/mtproto/rpc/send_code.rb +0 -41
  86. data/lib/mtproto/rpc/send_message.rb +0 -47
  87. data/lib/mtproto/rpc/sign_in.rb +0 -48
  88. data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
  89. data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
  90. data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
  91. data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
  92. data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
  93. data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
  94. data/lib/mtproto/type/code_settings.rb +0 -25
  95. data/lib/mtproto/type/config.rb +0 -124
  96. data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
  97. data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
  98. data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
  99. data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
  100. data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
  101. data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
  102. data/lib/mtproto/type/rpc/help/config.rb +0 -35
  103. data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
  104. data/lib/mtproto/type/rpc/init_connection.rb +0 -28
  105. data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
  106. data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
  107. data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
  108. data/lib/mtproto/type/rpc/ping.rb +0 -18
  109. data/lib/mtproto/type/rpc/pong.rb +0 -46
  110. data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
  111. data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
  112. data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
  113. data/lib/mtproto/type/rpc/updates/state.rb +0 -59
  114. data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
  115. data/lib/mtproto/type/rpc/users/users.rb +0 -99
  116. data/lib/mtproto/type/sent_code.rb +0 -128
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class Update
6
+ CONSTRUCTOR_NEW_MESSAGE = 0x1f2b0afd
7
+ MESSAGE_CONSTRUCTOR = 0x9815cec8
8
+
9
+ attr_reader :type, :text
10
+
11
+ def initialize(type, text: nil)
12
+ @type = type
13
+ @text = text
14
+ end
15
+
16
+ def self.parse(bytes)
17
+ constructor_id = bytes[0, 4].unpack1('L<')
18
+ type = type_name(constructor_id)
19
+
20
+ case constructor_id
21
+ when CONSTRUCTOR_NEW_MESSAGE
22
+ text = extract_new_message_text(bytes)
23
+ new(type, text: text)
24
+ else
25
+ new(type)
26
+ end
27
+ end
28
+
29
+ class << self
30
+ private
31
+
32
+ def type_name(constructor_id)
33
+ name = Constructors::NAMES.fetch(constructor_id)
34
+ name.sub(/^update/, '')
35
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
36
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
37
+ .downcase
38
+ end
39
+
40
+ def extract_new_message_text(bytes)
41
+ offset = 4 # skip updateNewMessage constructor
42
+
43
+ msg_constructor = bytes[offset, 4].unpack1('L<')
44
+ return nil unless msg_constructor == MESSAGE_CONSTRUCTOR
45
+
46
+ offset += 4
47
+ flags = bytes[offset, 4].unpack1('L<')
48
+ offset += 4
49
+ flags2 = bytes[offset, 4].unpack1('L<')
50
+ offset += 4
51
+ offset += 4 # id
52
+
53
+ offset += 12 if flags & (1 << 8) != 0 # from_id: Peer
54
+ offset += 12 # peer_id: Peer
55
+ offset += 12 if flags & (1 << 28) != 0 # saved_peer_id: Peer
56
+ return nil if flags & (1 << 2) != 0 # fwd_from: variable length
57
+
58
+ offset += 8 if flags & (1 << 11) != 0 # via_bot_id: long
59
+ offset += 8 if flags2 & 1 != 0 # via_business_bot_id: long
60
+ return nil if flags & (1 << 3) != 0 # reply_to: variable length
61
+
62
+ offset += 4 # date
63
+ read_tl_string(bytes, offset)
64
+ end
65
+
66
+ def read_tl_string(bytes, offset)
67
+ first_byte = bytes.getbyte(offset)
68
+ if first_byte == 254
69
+ len = bytes.getbyte(offset + 1) |
70
+ (bytes.getbyte(offset + 2) << 8) |
71
+ (bytes.getbyte(offset + 3) << 16)
72
+ bytes[offset + 4, len]
73
+ else
74
+ bytes[offset + 1, first_byte]
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class UpdateShort
6
+ CONSTRUCTOR = 0x78d4dec1
7
+
8
+ attr_reader :update, :date
9
+
10
+ def initialize(update, date)
11
+ @update = update
12
+ @date = date
13
+ end
14
+
15
+ def self.parse(bytes)
16
+ date = bytes[-4, 4].unpack1('l<')
17
+ update = Update.parse(bytes[4...-4])
18
+ new(update, date)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class UpdateShortMessage
6
+ CONSTRUCTOR = 0x313bc7f8
7
+
8
+ attr_reader :flags, :id, :user_id, :text, :pts, :pts_count, :date
9
+
10
+ def initialize(flags:, id:, user_id:, text:, pts:, pts_count:, date:)
11
+ @flags = flags
12
+ @id = id
13
+ @user_id = user_id
14
+ @text = text
15
+ @pts = pts
16
+ @pts_count = pts_count
17
+ @date = date
18
+ end
19
+
20
+ def out?
21
+ flags & (1 << 1) != 0
22
+ end
23
+
24
+ def mentioned?
25
+ flags & (1 << 4) != 0
26
+ end
27
+
28
+ def self.parse(bytes)
29
+ offset = 4 # skip constructor
30
+ flags = bytes[offset, 4].unpack1('L<')
31
+ offset += 4
32
+ id = bytes[offset, 4].unpack1('l<')
33
+ offset += 4
34
+ user_id = bytes[offset, 8].unpack1('q<')
35
+ offset += 8
36
+ text, offset = read_tl_string(bytes, offset)
37
+ pts = bytes[offset, 4].unpack1('l<')
38
+ offset += 4
39
+ pts_count = bytes[offset, 4].unpack1('l<')
40
+ offset += 4
41
+ date = bytes[offset, 4].unpack1('l<')
42
+
43
+ new(flags: flags, id: id, user_id: user_id, text: text,
44
+ pts: pts, pts_count: pts_count, date: date)
45
+ end
46
+
47
+ class << self
48
+ private
49
+
50
+ def read_tl_string(bytes, offset)
51
+ first_byte = bytes.getbyte(offset)
52
+ if first_byte == 254
53
+ len = bytes.getbyte(offset + 1) |
54
+ (bytes.getbyte(offset + 2) << 8) |
55
+ (bytes.getbyte(offset + 3) << 16)
56
+ total = 4 + len
57
+ else
58
+ len = first_byte
59
+ total = 1 + len
60
+ end
61
+ padding = (4 - (total % 4)) % 4
62
+ [bytes[offset + (total - len), len], offset + total + padding]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class UpdatesDifference
6
+ CONSTRUCTOR_EMPTY = 0x5d75a138
7
+ CONSTRUCTOR_DIFFERENCE = 0x00f49ca0
8
+ CONSTRUCTOR_SLICE = 0xa8fb1981
9
+ CONSTRUCTOR_TOO_LONG = 0x4afe8f6d
10
+
11
+ attr_reader :type, :date, :seq, :new_messages, :users, :pts, :state
12
+
13
+ def initialize(type:, date: nil, seq: nil, new_messages: [], users: [], pts: nil, state: nil)
14
+ @type = type
15
+ @date = date
16
+ @seq = seq
17
+ @new_messages = new_messages
18
+ @users = users
19
+ @pts = pts
20
+ @state = state
21
+ end
22
+
23
+ def self.parse(data)
24
+ constructor = data[0, 4].unpack1('L<')
25
+
26
+ case constructor
27
+ when CONSTRUCTOR_EMPTY
28
+ parse_empty(data)
29
+ when CONSTRUCTOR_DIFFERENCE, CONSTRUCTOR_SLICE
30
+ parse_difference(data, constructor)
31
+ when CONSTRUCTOR_TOO_LONG
32
+ new(type: :too_long, pts: data[4, 4].unpack1('L<'))
33
+ else
34
+ warn "Unknown Difference constructor: 0x#{constructor.to_s(16)}, treating as empty"
35
+ new(type: :empty, date: Time.now.to_i, seq: 0)
36
+ end
37
+ end
38
+
39
+ class << self
40
+ private
41
+
42
+ def parse_empty(data)
43
+ new(
44
+ type: :empty,
45
+ date: data[4, 4].unpack1('L<'),
46
+ seq: data[8, 4].unpack1('L<')
47
+ )
48
+ end
49
+
50
+ def parse_difference(data, constructor)
51
+ offset = 4
52
+
53
+ messages, offset = parse_messages_vector(data, offset)
54
+ offset = skip_vector(data, offset) # encrypted_messages
55
+ offset = skip_vector(data, offset) # other_updates
56
+ offset = skip_vector(data, offset) # chats
57
+ users, = parse_users_vector(data, offset)
58
+
59
+ new(
60
+ type: constructor == CONSTRUCTOR_DIFFERENCE ? :difference : :slice,
61
+ new_messages: messages,
62
+ users: users
63
+ )
64
+ end
65
+
66
+ def parse_messages_vector(data, offset)
67
+ offset += 4 # vector constructor
68
+ count = data[offset, 4].unpack1('L<')
69
+ offset += 4
70
+
71
+ messages = []
72
+ count.times do
73
+ msg, offset = parse_message(data, offset)
74
+ messages << msg if msg
75
+ end
76
+ [messages, offset]
77
+ end
78
+
79
+ def parse_message(data, offset)
80
+ msg_constructor = data[offset, 4].unpack1('L<')
81
+ offset += 4
82
+
83
+ return [nil, offset] unless msg_constructor == 0x452c0e65
84
+
85
+ flags = data[offset, 4].unpack1('L<')
86
+ offset += 4
87
+ id = data[offset, 4].unpack1('L<')
88
+ offset += 4
89
+ offset += 12 if flags.anybits?(1 << 8) # from_id
90
+ offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
91
+ offset += 12 # peer_id
92
+ offset += 8 if flags.anybits?(1 << 11) # via_bot_id
93
+ date = data[offset, 4].unpack1('L<')
94
+ offset += 4
95
+ message_text, offset = read_tl_string(data, offset)
96
+
97
+ [{ id: id, date: date, message: message_text, flags: flags }, offset]
98
+ rescue StandardError
99
+ [nil, offset]
100
+ end
101
+
102
+ def parse_users_vector(data, offset)
103
+ offset += 4 # vector constructor
104
+ count = data[offset, 4].unpack1('L<')
105
+ offset += 4
106
+
107
+ users = []
108
+ count.times do
109
+ user_constructor = data[offset, 4].unpack1('L<')
110
+ offset += 4
111
+ next unless user_constructor == 0x20b1422
112
+
113
+ flags = data[offset, 4].unpack1('L<')
114
+ offset += 4
115
+ offset += 4 # flags2
116
+ user_id = data[offset, 8].unpack1('Q<')
117
+ offset += 8
118
+
119
+ access_hash = nil
120
+ if flags.anybits?(1 << 0)
121
+ access_hash = data[offset, 8].unpack1('Q<')
122
+ offset += 8
123
+ end
124
+
125
+ users << { id: user_id, access_hash: access_hash }
126
+ rescue StandardError
127
+ break
128
+ end
129
+ [users, offset]
130
+ end
131
+
132
+ def skip_vector(data, offset)
133
+ offset += 4 # vector constructor
134
+ count = data[offset, 4].unpack1('L<')
135
+ offset += 4
136
+ count.times { offset += 4 }
137
+ offset
138
+ end
139
+
140
+ def read_tl_string(data, offset)
141
+ first_byte = data.getbyte(offset)
142
+ if first_byte == 254
143
+ len = data.getbyte(offset + 1) |
144
+ (data.getbyte(offset + 2) << 8) |
145
+ (data.getbyte(offset + 3) << 16)
146
+ total = 4 + len
147
+ else
148
+ len = first_byte
149
+ total = 1 + len
150
+ end
151
+ padding = (4 - (total % 4)) % 4
152
+ [data[offset + (total - len), len], offset + total + padding]
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class UpdatesState
6
+ CONSTRUCTOR = 0xa56c2a3e
7
+
8
+ attr_accessor :pts, :qts, :date, :seq, :unread_count
9
+
10
+ def initialize(pts:, qts:, date:, seq:, unread_count:)
11
+ @pts = pts
12
+ @qts = qts
13
+ @date = date
14
+ @seq = seq
15
+ @unread_count = unread_count
16
+ end
17
+
18
+ def self.parse(data)
19
+ constructor = data[0, 4].unpack1('L<')
20
+ raise UnexpectedConstructorError, constructor unless constructor == CONSTRUCTOR
21
+
22
+ offset = 4
23
+ pts = data[offset, 4].unpack1('L<')
24
+ offset += 4
25
+ qts = data[offset, 4].unpack1('L<')
26
+ offset += 4
27
+ date = data[offset, 4].unpack1('L<')
28
+ offset += 4
29
+ seq = data[offset, 4].unpack1('L<')
30
+ offset += 4
31
+ unread_count = data[offset, 4].unpack1('L<')
32
+
33
+ new(pts: pts, qts: qts, date: date, seq: seq, unread_count: unread_count)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class Users
6
+ VECTOR_CONSTRUCTOR = 0x1cb5c415
7
+ USER_CONSTRUCTOR = 0x020b1422
8
+
9
+ User = Struct.new(:id, :access_hash, :first_name, :last_name, :username, :phone, :flags, :flags2,
10
+ keyword_init: true)
11
+
12
+ def self.parse(data)
13
+ constructor = data[0, 4].unpack1('L<')
14
+ raise "Expected Vector constructor, got 0x#{constructor.to_s(16)}" unless constructor == VECTOR_CONSTRUCTOR
15
+
16
+ offset = 4
17
+ count = data[offset, 4].unpack1('L<')
18
+ offset += 4
19
+
20
+ users = []
21
+ count.times do
22
+ user_constructor = data[offset, 4].unpack1('L<')
23
+ offset += 4
24
+ next unless user_constructor == USER_CONSTRUCTOR
25
+
26
+ flags = data[offset, 4].unpack1('L<')
27
+ offset += 4
28
+
29
+ flags2 = data[offset, 4].unpack1('L<')
30
+ offset += 4
31
+
32
+ id = data[offset, 8].unpack1('Q<')
33
+ offset += 8
34
+
35
+ access_hash = nil
36
+ if flags.anybits?(1 << 0)
37
+ access_hash = data[offset, 8].unpack1('Q<')
38
+ offset += 8
39
+ end
40
+
41
+ first_name = nil
42
+ first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
43
+
44
+ last_name = nil
45
+ last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
46
+
47
+ username = nil
48
+ username, offset = read_tl_string(data, offset) if flags.anybits?(1 << 3)
49
+
50
+ phone = nil
51
+ phone, offset = read_tl_string(data, offset) if flags.anybits?(1 << 4)
52
+
53
+ users << User.new(
54
+ id: id, access_hash: access_hash,
55
+ first_name: first_name, last_name: last_name,
56
+ username: username, phone: phone,
57
+ flags: flags, flags2: flags2
58
+ )
59
+
60
+ break
61
+ end
62
+
63
+ users
64
+ end
65
+
66
+ class << self
67
+ private
68
+
69
+ def read_tl_string(data, offset)
70
+ first_byte = data.getbyte(offset)
71
+ if first_byte == 254
72
+ len = data.getbyte(offset + 1) |
73
+ (data.getbyte(offset + 2) << 8) |
74
+ (data.getbyte(offset + 3) << 16)
75
+ total = 4 + len
76
+ else
77
+ len = first_byte
78
+ total = 1 + len
79
+ end
80
+ padding = (4 - (total % 4)) % 4
81
+ [data[offset + (total - len), len], offset + total + padding]
82
+ end
83
+ end
84
+ end
85
+ end
86
+ 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