torrenter 0.0.6 → 0.0.7
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/bin/torrenter +5 -2
- data/lib/torrenter.rb +17 -42
- data/lib/torrenter/{message/message_types.rb → constants.rb} +1 -0
- data/lib/torrenter/peer.rb +18 -27
- data/lib/torrenter/peer/buffer_state.rb +164 -0
- data/lib/torrenter/reactor.rb +19 -168
- data/lib/torrenter/torrent_reader.rb +48 -54
- data/lib/torrenter/torrent_reader/piece.rb +125 -0
- data/lib/torrenter/torrent_reader/piece_constructor.rb +81 -0
- data/lib/torrenter/torrent_reader/piece_index.rb +87 -0
- data/lib/torrenter/tracker.rb +13 -9
- data/lib/torrenter/tracker/http_tracker.rb +41 -0
- data/lib/torrenter/tracker/udp_tracker.rb +107 -0
- data/lib/torrenter/version.rb +1 -1
- metadata +9 -11
- data/lib/torrenter/file_creator.rb +0 -0
- data/lib/torrenter/http_tracker.rb +0 -44
- data/lib/torrenter/message/messager.rb +0 -164
- data/lib/torrenter/peer_state.rb +0 -57
- data/lib/torrenter/setup.rb +0 -10
- data/lib/torrenter/test_msg.rb +0 -166
- data/lib/torrenter/test_reactor.rb +0 -219
- data/lib/torrenter/udp_tracker.rb +0 -96
@@ -0,0 +1,41 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class Tracker
|
3
|
+
|
4
|
+
attr_reader :response, :address
|
5
|
+
|
6
|
+
class HTTPTracker < Tracker
|
7
|
+
attr_reader :response
|
8
|
+
def initialize(url, params)
|
9
|
+
@address = URI(url)
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
@address.query = URI.encode_www_form(@params)
|
15
|
+
begin
|
16
|
+
@response = BEncode.load(Net::HTTP.get(@address))
|
17
|
+
rescue Exception => e
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
return self
|
22
|
+
end
|
23
|
+
|
24
|
+
def peer_list
|
25
|
+
format_peers(peers)
|
26
|
+
end
|
27
|
+
|
28
|
+
def connect_interval
|
29
|
+
@response['min interval']
|
30
|
+
end
|
31
|
+
|
32
|
+
def peers
|
33
|
+
@response['peers']
|
34
|
+
end
|
35
|
+
|
36
|
+
def connected?
|
37
|
+
@response
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class Tracker
|
3
|
+
class UDPTracker < Tracker
|
4
|
+
|
5
|
+
attr_reader :socket, :response, :interval
|
6
|
+
|
7
|
+
def initialize(tracker, params)
|
8
|
+
@url = tracker[/(?<=udp\:\/\/).+(?=\:\d+)/]
|
9
|
+
@port = tracker[/\d+$/].to_i
|
10
|
+
@socket = UDPSocket.new
|
11
|
+
@connection_id = [0x41727101980].pack("Q>")
|
12
|
+
@params = params
|
13
|
+
end
|
14
|
+
|
15
|
+
def interval?
|
16
|
+
interval % Time.now.to_i == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def segment(e, enc="I>")
|
20
|
+
[e].pack(enc)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect
|
24
|
+
@transaction_id = segment(rand(10000), "I>")
|
25
|
+
@socket.connect(ip_addr, @port)
|
26
|
+
begin
|
27
|
+
send_msg(connect_msg)
|
28
|
+
read_response
|
29
|
+
bound_peers
|
30
|
+
rescue
|
31
|
+
false
|
32
|
+
end
|
33
|
+
return self
|
34
|
+
end
|
35
|
+
|
36
|
+
def send_msg(msg)
|
37
|
+
begin
|
38
|
+
@socket.send(msg, 0)
|
39
|
+
rescue
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def connected?
|
45
|
+
@response
|
46
|
+
end
|
47
|
+
|
48
|
+
def ip_addr
|
49
|
+
Socket.getaddrinfo(@url, @port)[0][3]
|
50
|
+
end
|
51
|
+
|
52
|
+
def bound_peers
|
53
|
+
@connection_id = @response[-8..-1]
|
54
|
+
@transaction_id = [rand(10000)].pack("I>")
|
55
|
+
send_msg(announce_msg)
|
56
|
+
|
57
|
+
read_response
|
58
|
+
|
59
|
+
parse_announce if @response[0..3] == segment(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def peer_list
|
63
|
+
format_peers(@response)
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_announce
|
67
|
+
if @response[4..7] == @transaction_id
|
68
|
+
@interval = @response[8..11].unpack("I>").first
|
69
|
+
res = @response.slice!(0..11)
|
70
|
+
@leechers = @response.slice!(0..3).unpack("I>").first
|
71
|
+
@seeders = @response.slice!(0..3).unpack("I>").first
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_message
|
76
|
+
begin
|
77
|
+
@socket.send(@msg, 0)
|
78
|
+
rescue *EXCEPTIONS
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_response
|
83
|
+
begin
|
84
|
+
@response = @socket.recv(1028)
|
85
|
+
rescue Exception => e
|
86
|
+
e
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def connect_match?
|
91
|
+
data[0] == (segment(0) + @transaction_id + @connection_id)
|
92
|
+
end
|
93
|
+
|
94
|
+
def announce_input
|
95
|
+
@connection_id + segment(1) + @transaction_id + @params[:info_hash] + PEER_ID
|
96
|
+
end
|
97
|
+
|
98
|
+
def connect_msg
|
99
|
+
@connection_id + segment(0) + @transaction_id
|
100
|
+
end
|
101
|
+
|
102
|
+
def announce_msg
|
103
|
+
announce_input + (segment(0, "Q>") * 3) + (segment(0) * 3) + segment(-1) + segment(@socket.addr[1], ">S")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/torrenter/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: torrenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- wismer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: BitTorrent Client written in Ruby
|
14
14
|
email:
|
@@ -20,19 +20,17 @@ extra_rdoc_files: []
|
|
20
20
|
files:
|
21
21
|
- bin/torrenter
|
22
22
|
- lib/torrenter.rb
|
23
|
-
- lib/torrenter/
|
24
|
-
- lib/torrenter/http_tracker.rb
|
25
|
-
- lib/torrenter/message/message_types.rb
|
26
|
-
- lib/torrenter/message/messager.rb
|
23
|
+
- lib/torrenter/constants.rb
|
27
24
|
- lib/torrenter/peer.rb
|
28
|
-
- lib/torrenter/
|
25
|
+
- lib/torrenter/peer/buffer_state.rb
|
29
26
|
- lib/torrenter/reactor.rb
|
30
|
-
- lib/torrenter/setup.rb
|
31
|
-
- lib/torrenter/test_msg.rb
|
32
|
-
- lib/torrenter/test_reactor.rb
|
33
27
|
- lib/torrenter/torrent_reader.rb
|
28
|
+
- lib/torrenter/torrent_reader/piece.rb
|
29
|
+
- lib/torrenter/torrent_reader/piece_constructor.rb
|
30
|
+
- lib/torrenter/torrent_reader/piece_index.rb
|
34
31
|
- lib/torrenter/tracker.rb
|
35
|
-
- lib/torrenter/
|
32
|
+
- lib/torrenter/tracker/http_tracker.rb
|
33
|
+
- lib/torrenter/tracker/udp_tracker.rb
|
36
34
|
- lib/torrenter/version.rb
|
37
35
|
homepage: http://wismer.github.io
|
38
36
|
licenses:
|
File without changes
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
class HTTPTracker < TorrentReader
|
3
|
-
attr_reader :response
|
4
|
-
def initialize(tracker, stream)
|
5
|
-
super(stream)
|
6
|
-
@address = URI(tracker)
|
7
|
-
end
|
8
|
-
|
9
|
-
def connect
|
10
|
-
@address.query = URI.encode_www_form(tracker_params)
|
11
|
-
@response =
|
12
|
-
begin
|
13
|
-
BEncode.load(Net::HTTP.get(@address))
|
14
|
-
rescue
|
15
|
-
false
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def connect_interval
|
20
|
-
@response['min_interval']
|
21
|
-
end
|
22
|
-
|
23
|
-
def address_hashes
|
24
|
-
@response['peers']
|
25
|
-
end
|
26
|
-
|
27
|
-
def tracker_params
|
28
|
-
{
|
29
|
-
:info_hash => info_hash,
|
30
|
-
:peer_id => PEER_ID,
|
31
|
-
:left => piece_length,
|
32
|
-
:pieces => file_list
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
def bound_peers
|
37
|
-
peer_list(@response['peers'])
|
38
|
-
end
|
39
|
-
|
40
|
-
def connected?
|
41
|
-
@response
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,164 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
# KEEP_ALIVE = "\x00\x00\x00\x00"
|
3
|
-
# BLOCK = 2**14
|
4
|
-
# these methods get mixed in with the Peer class as a way to help
|
5
|
-
# organize and parse the byte-encoded data. The intention is to shorten
|
6
|
-
# and shrink the complexity of the Peer class.
|
7
|
-
# the following methods are responsible solely for data retrieval and data transmission
|
8
|
-
|
9
|
-
def state(master_index, remaining, &block)
|
10
|
-
@remaining = remaining
|
11
|
-
|
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
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def completed?
|
38
|
-
piece_data.bytesize == @piece_length || piece_data.bytesize == @remaining
|
39
|
-
end
|
40
|
-
|
41
|
-
def piece_data
|
42
|
-
@blocks.join('')
|
43
|
-
end
|
44
|
-
|
45
|
-
def choke_message
|
46
|
-
@peer_state = false
|
47
|
-
end
|
48
|
-
|
49
|
-
def clear_msg
|
50
|
-
@buffer.slice!(0..@length + 3)
|
51
|
-
end
|
52
|
-
|
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))
|
56
|
-
end
|
57
|
-
|
58
|
-
def pack(msg)
|
59
|
-
[msg].pack("I>")
|
60
|
-
end
|
61
|
-
|
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?
|
66
|
-
end
|
67
|
-
|
68
|
-
def process_have(msg)
|
69
|
-
@piece_index[msg[5..-1].unpack("C*").last] = :free
|
70
|
-
send_interested if @buffer.empty?
|
71
|
-
end
|
72
|
-
|
73
|
-
def request_piece(index)
|
74
|
-
@index = index
|
75
|
-
@piece_index[@index] = :downloading
|
76
|
-
|
77
|
-
request_message
|
78
|
-
end
|
79
|
-
|
80
|
-
def send_interested
|
81
|
-
send_data("\x00\x00\x00\x01\x02")
|
82
|
-
end
|
83
|
-
|
84
|
-
def buffer_length?
|
85
|
-
@buffer.bytesize >= @length + 4
|
86
|
-
end
|
87
|
-
|
88
|
-
def process_piece(&block)
|
89
|
-
binding.pry if @length < BLOCK
|
90
|
-
if buffer_length?
|
91
|
-
if buffer_complete?
|
92
|
-
pack_buffer
|
93
|
-
end
|
94
|
-
|
95
|
-
if @blocks.join('').bytesize < @piece_length
|
96
|
-
diff = @remaining - @blocks.join('').bytesize
|
97
|
-
if diff < BLOCK
|
98
|
-
request_message(diff)
|
99
|
-
else
|
100
|
-
request_message
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
block.call if completed?
|
105
|
-
end
|
106
|
-
recv_data
|
107
|
-
end
|
108
|
-
|
109
|
-
def mark_complete
|
110
|
-
@blocks = []
|
111
|
-
@piece_index[@index] = :downloaded
|
112
|
-
end
|
113
|
-
|
114
|
-
def pack_buffer
|
115
|
-
@blocks << @buffer.slice!(13..-1)
|
116
|
-
@buffer.clear
|
117
|
-
end
|
118
|
-
|
119
|
-
def buffer_complete?
|
120
|
-
(@buffer.bytesize - 13) == BLOCK || (@buffer.bytesize - 13) == @length - 9
|
121
|
-
end
|
122
|
-
|
123
|
-
# will need a last piece sort of thing inserted in here
|
124
|
-
|
125
|
-
def process_handshake
|
126
|
-
if hash_check?
|
127
|
-
@buffer.slice!(0..67)
|
128
|
-
else
|
129
|
-
@peer_state = false
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def hash_check?
|
134
|
-
@buffer[28..47] == @info_hash
|
135
|
-
end
|
136
|
-
|
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
|
149
|
-
end
|
150
|
-
|
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
|
163
|
-
end
|
164
|
-
end
|
data/lib/torrenter/peer_state.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
# The buffer state should be responsible ONLY for the handling of non-metadata bytes.
|
3
|
-
# the messaging behavior should and ought to remain with the peer class, though
|
4
|
-
# in truth, it may be better if I didnt do that.
|
5
|
-
|
6
|
-
# So, if the state of the buffer reaches a certain step in its process involving the piece
|
7
|
-
# the buffer state should be fired off.
|
8
|
-
|
9
|
-
# instead of initialiazing it with the buffer,
|
10
|
-
|
11
|
-
class BufferState
|
12
|
-
|
13
|
-
def intialize(socket)
|
14
|
-
@socket = socket
|
15
|
-
@buffer = ''
|
16
|
-
end
|
17
|
-
|
18
|
-
# include Torrenter
|
19
|
-
|
20
|
-
def state(buffer, master_index)
|
21
|
-
buffer_id = buffer[4]
|
22
|
-
if buffer_id.nil?
|
23
|
-
buffer.slice!(0..3)
|
24
|
-
else
|
25
|
-
@length = buffer[0..3].unpack("N>")
|
26
|
-
case buffer_id
|
27
|
-
when BITFIELD then process_bitfield
|
28
|
-
when HAVE then process_have
|
29
|
-
when INTERESTED then process_interested
|
30
|
-
when PIECE then process_piece
|
31
|
-
when CHOKE then choke_message
|
32
|
-
when HANDSHAKE then process_handshake
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def process_bitfield
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def process_have
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
def process_piece
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def process_handshake
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
def recv_data
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|