torrenter 0.0.5 → 0.0.6
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/lib/torrenter.rb +38 -3
- data/lib/torrenter/file_creator.rb +0 -0
- data/lib/torrenter/http_tracker.rb +16 -12
- data/lib/torrenter/message/message_types.rb +1 -1
- data/lib/torrenter/message/messager.rb +108 -171
- data/lib/torrenter/peer.rb +28 -26
- data/lib/torrenter/peer_state.rb +44 -4
- data/lib/torrenter/reactor.rb +158 -92
- data/lib/torrenter/setup.rb +10 -0
- data/lib/torrenter/test_msg.rb +166 -0
- data/lib/torrenter/test_reactor.rb +219 -0
- data/lib/torrenter/torrent_reader.rb +87 -51
- data/lib/torrenter/tracker.rb +13 -0
- data/lib/torrenter/udp_tracker.rb +12 -10
- data/lib/torrenter/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b84f8e49f69b5654aedf1edd2b894bf3d9d9a612
|
4
|
+
data.tar.gz: 6aa38459649cc8e4c8ce770799f46a161967d457
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29dbc732d5bb03d5fb76362bb5825622c9ecbf14cc03cb352ddc3d780238154a1b60084fcbcb6b7f9ab9f00a75b401516e3bd243d6822d56ce129180ac225d68
|
7
|
+
data.tar.gz: 80d1d9d22e3186ebb45e555f70ae272c7beb472c39e3ea80796a83527ab50529e4b60a1bd7ed9b281b4fb243ce1cb2899af569a7d522e957c92d0b61046f5477
|
data/lib/torrenter.rb
CHANGED
@@ -6,18 +6,53 @@ require 'pry'
|
|
6
6
|
require 'torrenter/message/messager'
|
7
7
|
require 'torrenter/message/message_types'
|
8
8
|
require 'torrenter/peer'
|
9
|
-
require 'torrenter/reactor'
|
10
9
|
require 'torrenter/torrent_reader'
|
10
|
+
require 'torrenter/reactor'
|
11
11
|
require 'torrenter/http_tracker'
|
12
12
|
require 'torrenter/udp_tracker'
|
13
13
|
|
14
|
+
# reader just reads the torrent file
|
15
|
+
# trackers just get peer ips and ports
|
16
|
+
# peers just hold peer data
|
17
|
+
# reactor uses the peer data to connect to the peers
|
18
|
+
# the buffer state should be a class on its own.
|
19
|
+
|
20
|
+
|
14
21
|
module Torrenter
|
15
22
|
class Torrent
|
16
23
|
def start(file)
|
17
24
|
IO.write($data_dump, '', 0) if !File.exists?($data_dump)
|
18
25
|
stream = BEncode.load_file(file)
|
19
|
-
|
20
|
-
|
26
|
+
torrent_file = Torrenter::TorrentReader.new(stream)
|
27
|
+
trackers = torrent_file.access_trackers
|
28
|
+
|
29
|
+
# returns an array of initialized UDP and/or HTTP Tracker class objects
|
30
|
+
|
31
|
+
loop do
|
32
|
+
@tracker = trackers.shift
|
33
|
+
if @tracker
|
34
|
+
begin
|
35
|
+
Timeout::timeout(10) { @tracker.connect }
|
36
|
+
rescue Timeout::Error
|
37
|
+
puts 'Tracker not responding. Trying next.'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if @tracker.connected?
|
42
|
+
break
|
43
|
+
elsif trackers.empty?
|
44
|
+
raise 'Trackers non-responsive'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
peers = @tracker.bound_peers
|
49
|
+
reactor = Reactor.new(peers, stream)
|
50
|
+
reactor.check_for_existing_data
|
51
|
+
unless reactor.finished?
|
52
|
+
reactor.message_reactor
|
53
|
+
else
|
54
|
+
reactor.unpack_data
|
55
|
+
end
|
21
56
|
end
|
22
57
|
end
|
23
58
|
end
|
File without changes
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Torrenter
|
2
2
|
class HTTPTracker < TorrentReader
|
3
|
-
|
4
|
-
def initialize(
|
5
|
-
@address = URI(address)
|
3
|
+
attr_reader :response
|
4
|
+
def initialize(tracker, stream)
|
6
5
|
super(stream)
|
6
|
+
@address = URI(tracker)
|
7
7
|
end
|
8
8
|
|
9
9
|
def connect
|
10
10
|
@address.query = URI.encode_www_form(tracker_params)
|
11
|
-
@response =
|
11
|
+
@response =
|
12
12
|
begin
|
13
13
|
BEncode.load(Net::HTTP.get(@address))
|
14
14
|
rescue
|
@@ -16,25 +16,29 @@ module Torrenter
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
@response['
|
19
|
+
def connect_interval
|
20
|
+
@response['min_interval']
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
|
23
|
+
def address_hashes
|
24
|
+
@response['peers']
|
25
25
|
end
|
26
26
|
|
27
27
|
def tracker_params
|
28
28
|
{
|
29
|
-
:info_hash =>
|
29
|
+
:info_hash => info_hash,
|
30
30
|
:peer_id => PEER_ID,
|
31
|
-
:left =>
|
32
|
-
:pieces =>
|
31
|
+
:left => piece_length,
|
32
|
+
:pieces => file_list
|
33
33
|
}
|
34
34
|
end
|
35
35
|
|
36
|
+
def bound_peers
|
37
|
+
peer_list(@response['peers'])
|
38
|
+
end
|
39
|
+
|
36
40
|
def connected?
|
37
41
|
@response
|
38
42
|
end
|
39
43
|
end
|
40
|
-
end
|
44
|
+
end
|
@@ -9,5 +9,5 @@ module Torrenter
|
|
9
9
|
PIECE = "\a"
|
10
10
|
CHOKE = "\x00"
|
11
11
|
PROTOCOL = "\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00"
|
12
|
-
EXCEPTIONS = [Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED, Errno::EPIPE, Errno::ECONNRESET]
|
12
|
+
EXCEPTIONS = [Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED, Errno::EPIPE, Errno::ECONNRESET, IO::EAGAINWaitWritable]
|
13
13
|
end
|
@@ -6,222 +6,159 @@ module Torrenter
|
|
6
6
|
# and shrink the complexity of the Peer class.
|
7
7
|
# the following methods are responsible solely for data retrieval and data transmission
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
Timeout::timeout(2) { @socket.sendmsg_nonblock(msg) }
|
12
|
-
rescue Timeout::Error
|
13
|
-
''
|
14
|
-
rescue *EXCEPTIONS
|
15
|
-
@status = false
|
16
|
-
''
|
17
|
-
end
|
18
|
-
end
|
9
|
+
def state(master_index, remaining, &block)
|
10
|
+
@remaining = remaining
|
19
11
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@
|
27
|
-
|
28
|
-
|
29
|
-
|
12
|
+
recv_data if @buffer.size <= 3
|
13
|
+
if @buffer[4].nil?
|
14
|
+
@buffer.slice!(0..3) if @buffer[0..3] == KEEP_ALIVE
|
15
|
+
else
|
16
|
+
@length = @buffer[0..3].unpack("N*").last
|
17
|
+
@buffer_id = @buffer[4]
|
18
|
+
case @buffer_id
|
19
|
+
when BITFIELD
|
20
|
+
process_bitfield(master_index.size, clear_msg)
|
21
|
+
when HAVE
|
22
|
+
process_have(clear_msg)
|
23
|
+
when INTERESTED
|
24
|
+
@buffer = ''
|
25
|
+
|
26
|
+
return :interested
|
27
|
+
when PIECE
|
28
|
+
process_piece { block.call @blocks.join(''), @index }
|
29
|
+
when CHOKE
|
30
|
+
choke_message
|
31
|
+
when HANDSHAKE
|
32
|
+
process_handshake
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# another message is sent out to attempt to fix the missing data)
|
37
|
-
|
38
|
-
|
39
|
-
# parse message will be the gatekeeper. If the buffer is ever low in the READ part
|
40
|
-
# of the torrent program, then it knows more data may be required.
|
41
|
-
# It will also be responsble for EVERY new message that gets received, whether
|
42
|
-
# its in the download sequence of messaging or in the actual handshake, it will
|
43
|
-
# control it that way.
|
37
|
+
def completed?
|
38
|
+
piece_data.bytesize == @piece_length || piece_data.bytesize == @remaining
|
39
|
+
end
|
44
40
|
|
45
|
-
|
41
|
+
def piece_data
|
42
|
+
@blocks.join('')
|
43
|
+
end
|
46
44
|
|
47
|
-
def
|
48
|
-
|
49
|
-
buffer.slice!(0)
|
50
|
-
@piece_index = buffer.slice!(0...len)
|
51
|
-
.unpack("B#{sha_list.size}")
|
52
|
-
.first
|
53
|
-
.split('')
|
54
|
-
.map { |bit| bit == '1' ? :available : :unavailable }
|
45
|
+
def choke_message
|
46
|
+
@peer_state = false
|
55
47
|
end
|
56
48
|
|
57
|
-
def
|
58
|
-
|
49
|
+
def clear_msg
|
50
|
+
@buffer.slice!(0..@length + 3)
|
59
51
|
end
|
60
52
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
@piece_index[index] = :available
|
65
|
-
buffer.slice!(0..8)
|
66
|
-
else
|
67
|
-
recv_data
|
68
|
-
end
|
69
|
-
send_interested if @buffer.empty?
|
53
|
+
def request_message(bytes=BLOCK)
|
54
|
+
@msg = "index: #{@index} offset: #{@blocks.size * BLOCK} bytes: #{bytes}"
|
55
|
+
send_data(pack(13) + "\x06" + pack(@index) + pack(@blocks.size * BLOCK) + pack(bytes))
|
70
56
|
end
|
71
57
|
|
72
|
-
|
58
|
+
def pack(msg)
|
59
|
+
[msg].pack("I>")
|
60
|
+
end
|
73
61
|
|
74
|
-
def
|
75
|
-
|
62
|
+
def process_bitfield(size, msg)
|
63
|
+
index = msg[5..-1].unpack("B#{size}").join.split('')
|
64
|
+
@piece_index = index.map { |bit| bit == '1' ? :free : :unavailable }
|
65
|
+
send_interested if @buffer.empty?
|
76
66
|
end
|
77
67
|
|
78
|
-
def
|
79
|
-
@
|
68
|
+
def process_have(msg)
|
69
|
+
@piece_index[msg[5..-1].unpack("C*").last] = :free
|
70
|
+
send_interested if @buffer.empty?
|
80
71
|
end
|
81
72
|
|
82
|
-
|
73
|
+
def request_piece(index)
|
74
|
+
@index = index
|
75
|
+
@piece_index[@index] = :downloading
|
83
76
|
|
84
|
-
|
85
|
-
@buffer.slice!(0..3).unpack("N*").first - 1
|
77
|
+
request_message
|
86
78
|
end
|
87
79
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def evaluate_index(master)
|
92
|
-
if @piece_index.any? { |chunk| chunk == :available }
|
93
|
-
@index = @piece_index.index(:available)
|
94
|
-
if master[@index] == :free
|
95
|
-
@piece_index[@index] = :downloading
|
96
|
-
master[@index] = :downloading
|
97
|
-
else
|
98
|
-
@piece_index[@index] = :downloaded
|
99
|
-
evaluate_index(master)
|
100
|
-
end
|
101
|
-
end
|
80
|
+
def send_interested
|
81
|
+
send_data("\x00\x00\x00\x01\x02")
|
102
82
|
end
|
103
83
|
|
104
|
-
def
|
105
|
-
|
106
|
-
if v == :downloaded
|
107
|
-
@piece_index[i] = :downloaded
|
108
|
-
end
|
109
|
-
end
|
84
|
+
def buffer_length?
|
85
|
+
@buffer.bytesize >= @length + 4
|
110
86
|
end
|
111
87
|
|
112
|
-
def
|
113
|
-
@
|
114
|
-
if
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
else
|
88
|
+
def process_piece(&block)
|
89
|
+
binding.pry if @length < BLOCK
|
90
|
+
if buffer_length?
|
91
|
+
if buffer_complete?
|
92
|
+
pack_buffer
|
93
|
+
end
|
119
94
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
modify_index(master)
|
125
|
-
evaluate_index(master)
|
126
|
-
request_message
|
127
|
-
when HAVE
|
128
|
-
parse_have
|
129
|
-
when BITFIELD
|
130
|
-
parse_bitfield
|
131
|
-
send_interested if buffer.empty?
|
132
|
-
when PIECE
|
133
|
-
@msg_length = buffer[0..3].unpack("N*").first + 4
|
134
|
-
|
135
|
-
if block_finished?
|
136
|
-
buffer.slice!(0..12)
|
137
|
-
|
138
|
-
# the metadata is sliced off.
|
139
|
-
|
140
|
-
@offset += (@msg_length - 13)
|
141
|
-
|
142
|
-
# buffer is reduced
|
143
|
-
|
144
|
-
pack_buffer
|
145
|
-
# that means the bytes for that block have been collected entirely.
|
146
|
-
# the buffer becomes empty
|
147
|
-
if pieces_remaining(master) > 1
|
148
|
-
|
149
|
-
if piece_size == @piece_len
|
150
|
-
pack_file(master)
|
151
|
-
evaluate_index(master)
|
152
|
-
request_message
|
153
|
-
else
|
154
|
-
request_message
|
155
|
-
end
|
156
|
-
elsif (File.size($data_dump) + piece_size) == total_file_size
|
157
|
-
pack_file(master)
|
158
|
-
else
|
159
|
-
if bytes_remaining >= BLOCK
|
160
|
-
request_message
|
161
|
-
else
|
162
|
-
request_message(bytes_remaining)
|
163
|
-
end
|
164
|
-
end
|
95
|
+
if @blocks.join('').bytesize < @piece_length
|
96
|
+
diff = @remaining - @blocks.join('').bytesize
|
97
|
+
if diff < BLOCK
|
98
|
+
request_message(diff)
|
165
99
|
else
|
166
|
-
|
100
|
+
request_message
|
167
101
|
end
|
168
|
-
# piece
|
169
|
-
when CHOKE
|
170
|
-
buffer.slice!(0..3)
|
171
|
-
send_interested
|
172
|
-
when HANDSHAKE
|
173
|
-
parse_handshake
|
174
102
|
end
|
103
|
+
|
104
|
+
block.call if completed?
|
175
105
|
end
|
106
|
+
recv_data
|
176
107
|
end
|
177
108
|
|
178
|
-
def
|
179
|
-
|
109
|
+
def mark_complete
|
110
|
+
@blocks = []
|
111
|
+
@piece_index[@index] = :downloaded
|
180
112
|
end
|
181
113
|
|
182
|
-
def
|
183
|
-
|
114
|
+
def pack_buffer
|
115
|
+
@blocks << @buffer.slice!(13..-1)
|
116
|
+
@buffer.clear
|
184
117
|
end
|
185
118
|
|
186
|
-
def
|
187
|
-
@
|
119
|
+
def buffer_complete?
|
120
|
+
(@buffer.bytesize - 13) == BLOCK || (@buffer.bytesize - 13) == @length - 9
|
188
121
|
end
|
189
122
|
|
190
|
-
|
191
|
-
@block_map << @buffer.slice!(0...(@msg_length - 13))
|
192
|
-
end
|
123
|
+
# will need a last piece sort of thing inserted in here
|
193
124
|
|
194
|
-
def
|
195
|
-
|
196
|
-
|
197
|
-
IO.write($data_dump, data, @index * @offset)
|
198
|
-
master[@index] = :downloaded
|
199
|
-
@piece_index[@index] = :downloaded
|
125
|
+
def process_handshake
|
126
|
+
if hash_check?
|
127
|
+
@buffer.slice!(0..67)
|
200
128
|
else
|
201
|
-
|
202
|
-
@piece_index[@index] = :downloaded
|
129
|
+
@peer_state = false
|
203
130
|
end
|
204
|
-
@block_map = []
|
205
|
-
@offset = 0
|
206
131
|
end
|
207
132
|
|
208
|
-
def
|
209
|
-
|
133
|
+
def hash_check?
|
134
|
+
@buffer[28..47] == @info_hash
|
210
135
|
end
|
211
136
|
|
212
|
-
def
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
137
|
+
def send_data(msg)
|
138
|
+
begin
|
139
|
+
Timeout::timeout(2) { @socket.sendmsg_nonblock(msg) }
|
140
|
+
rescue Timeout::Error
|
141
|
+
''
|
142
|
+
rescue IO::EAGAINWaitReadable
|
143
|
+
''
|
144
|
+
rescue *EXCEPTIONS
|
145
|
+
''
|
146
|
+
rescue Errno::ETIMEDOUT
|
147
|
+
@peer_state = false
|
148
|
+
end
|
222
149
|
end
|
223
150
|
|
224
|
-
def
|
225
|
-
|
151
|
+
def recv_data(bytes=BLOCK)
|
152
|
+
begin
|
153
|
+
Timeout::timeout(5) { @buffer << @socket.recv_nonblock(bytes) }
|
154
|
+
rescue Timeout::Error
|
155
|
+
''
|
156
|
+
rescue IO::EAGAINWaitReadable
|
157
|
+
''
|
158
|
+
rescue *EXCEPTIONS
|
159
|
+
''
|
160
|
+
rescue Errno::ETIMEDOUT
|
161
|
+
@peer_state = false
|
162
|
+
end
|
226
163
|
end
|
227
164
|
end
|