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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3773b22bb18ac1dc0b3ccd2994889a9f21bbc84
4
- data.tar.gz: cd433369ba505739ad4672dae2b5e81a8b048971
3
+ metadata.gz: b84f8e49f69b5654aedf1edd2b894bf3d9d9a612
4
+ data.tar.gz: 6aa38459649cc8e4c8ce770799f46a161967d457
5
5
  SHA512:
6
- metadata.gz: 0a25c88408740951f0400a2f723d5a02265a6d0a71e6af317b82913b3a58453f06bccd26af66349a8e276f25b56ca652b2ae7d1b392d98b7f4842cd9759ba233
7
- data.tar.gz: 30b43deacc081ce8475a5e1ac83e058f10fdc7782a188d835e6da602438714f9e2a46b851f9d6fad83b8a84d498d84f39e62f5baa8e9c67f8e16ab5c2be77ea1
6
+ metadata.gz: 29dbc732d5bb03d5fb76362bb5825622c9ecbf14cc03cb352ddc3d780238154a1b60084fcbcb6b7f9ab9f00a75b401516e3bd243d6822d56ce129180ac225d68
7
+ data.tar.gz: 80d1d9d22e3186ebb45e555f70ae272c7beb472c39e3ea80796a83527ab50529e4b60a1bd7ed9b281b4fb243ce1cb2899af569a7d522e957c92d0b61046f5477
@@ -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
- peers = Torrenter::TorrentReader.new(stream)
20
- peers.determine_protocol
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(address, stream)
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 address_hashes
20
- @response['peers']
19
+ def connect_interval
20
+ @response['min_interval']
21
21
  end
22
22
 
23
- def address_list
24
- parse_addresses(address_hashes, address_hashes.bytesize / 6)
23
+ def address_hashes
24
+ @response['peers']
25
25
  end
26
26
 
27
27
  def tracker_params
28
28
  {
29
- :info_hash => @sha,
29
+ :info_hash => info_hash,
30
30
  :peer_id => PEER_ID,
31
- :left => @piece_length,
32
- :pieces => @file_list
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 send_data(msg, opts={})
10
- begin
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
- def recv_data(bytes=BLOCK, opts={})
21
- begin
22
- Timeout::timeout(5) { buffer << @socket.recv_nonblock(bytes) }
23
- rescue Timeout::Error
24
- ''
25
- rescue *EXCEPTIONS
26
- @status = false
27
- ''
28
- rescue IO::EAGAINWaitReadable
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
- # the next few methods responsibility is for parsing the buffer.
34
- # They are responsible for the critical step of analyzing the buffer,
35
- # checking for message consistency from the peer (and if it fails a test
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
- # for now, I'm assuming that at least the bitfield will be sent in full
41
+ def piece_data
42
+ @blocks.join('')
43
+ end
46
44
 
47
- def parse_bitfield
48
- len = msg_len
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 send_interested
58
- send_data("\x00\x00\x00\x01\x02")
49
+ def clear_msg
50
+ @buffer.slice!(0..@length + 3)
59
51
  end
60
52
 
61
- def parse_have
62
- if buffer[5..8].bytesize == 4
63
- index = buffer[5..8].unpack("N*").first
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
- # because ruby.
58
+ def pack(msg)
59
+ [msg].pack("I>")
60
+ end
73
61
 
74
- def parse_handshake
75
- @buffer.slice!(0..67) if hash_match?
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 hash_match?
79
- @buffer.unpack("A*").first[28..47] == info_hash
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
- # the negative 1 modifier is for factoring in the id
73
+ def request_piece(index)
74
+ @index = index
75
+ @piece_index[@index] = :downloading
83
76
 
84
- def msg_len
85
- @buffer.slice!(0..3).unpack("N*").first - 1
77
+ request_message
86
78
  end
87
79
 
88
- # needs optimization. It evaluates the master index lazily with
89
- # no thought as to the 'rare pieces'
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 modify_index(master)
105
- master.each_with_index do |v,i|
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 state(master, blocks)
113
- @buffer_state = @buffer[4]
114
- if buffer.bytesize <= 3
115
- recv_data
116
- elsif buffer.bytesize == 4 && buffer[0..3] == KEEP_ALIVE
117
- buffer.slice!(0..3)
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
- # p @buffer[0..3].unp@buack("C*")
121
- case @buffer[4]
122
- when INTERESTED
123
- buffer.slice!(0..4)
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
- recv_data(@msg_length - buffer.bytesize)
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 pieces_remaining(master)
179
- master.count(:downloading) + master.count(:free)
109
+ def mark_complete
110
+ @blocks = []
111
+ @piece_index[@index] = :downloaded
180
112
  end
181
113
 
182
- def bytes_remaining
183
- (total_file_size - File.size($data_dump)) - piece_size
114
+ def pack_buffer
115
+ @blocks << @buffer.slice!(13..-1)
116
+ @buffer.clear
184
117
  end
185
118
 
186
- def piece_size
187
- @block_map.join.bytesize
119
+ def buffer_complete?
120
+ (@buffer.bytesize - 13) == BLOCK || (@buffer.bytesize - 13) == @length - 9
188
121
  end
189
122
 
190
- def pack_buffer
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 pack_file(master)
195
- data = @block_map.join
196
- if piece_verified?(data)
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
- master[@index] = :free
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 piece_verified?(data)
209
- Digest::SHA1.digest(data) == sha_list[@index]
133
+ def hash_check?
134
+ @buffer[28..47] == @info_hash
210
135
  end
211
136
 
212
- def request_message(bytes=BLOCK)
213
- send_data(pack(13) + "\x06" + pack(@index) + pack(@offset) + pack(bytes))
214
- end
215
-
216
- def pack(i)
217
- [i].pack("I>")
218
- end
219
-
220
- def block_finished?
221
- buffer.bytesize >= @msg_length
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 piece_finished?
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