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 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