torrenter 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|