torrenter 0.0.3 → 0.0.4
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 +8 -1
- data/lib/torrenter/http_tracker.rb +40 -0
- data/lib/torrenter/message/messager.rb +0 -1
- data/lib/torrenter/torrent_reader.rb +30 -94
- data/lib/torrenter/udp_tracker.rb +104 -0
- data/lib/torrenter/version.rb +1 -1
- metadata +4 -3
- data/lib/torrenter/udp.rb +0 -89
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 992e0e356ed722f0cb7131a5029b454e6fdba9e2
|
4
|
+
data.tar.gz: 64ae0a3e57481d38ac75668773303d2ab601fa91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb19b53d0d740111e96941af9a02a07352979196ead79e1c34c796f42fdcec5d1aab7d6527b309f8cea84dec1513cc6b76b680f02c562f14c8993b7fa7a0353e
|
7
|
+
data.tar.gz: a2870db5b56bfc2a887a2530ea924671fa677e0fb26ce0b80311148bf83a29b9457a2b8282e0ec9de832bfa8b401cecd9479eacac3441dd36f7ae342072e9273
|
data/lib/torrenter.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'bencode'
|
4
|
+
require 'fileutils'
|
5
|
+
|
1
6
|
require 'torrenter/message/messager'
|
2
7
|
require 'torrenter/message/message_types'
|
3
8
|
require 'torrenter/peer'
|
4
9
|
require 'torrenter/reactor'
|
5
|
-
require 'torrenter/udp'
|
6
10
|
require 'torrenter/torrent_reader'
|
11
|
+
require 'torrenter/http_tracker'
|
12
|
+
require 'torrenter/udp_tracker'
|
13
|
+
|
7
14
|
module Torrenter
|
8
15
|
class Torrent
|
9
16
|
def start(file)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class HTTPTracker < TorrentReader
|
3
|
+
|
4
|
+
def initialize(address, stream)
|
5
|
+
@address = URI(address)
|
6
|
+
super(stream)
|
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 address_hashes
|
20
|
+
@response['peers']
|
21
|
+
end
|
22
|
+
|
23
|
+
def address_list
|
24
|
+
parse_addresses(address_hashes, address_hashes.bytesize / 6)
|
25
|
+
end
|
26
|
+
|
27
|
+
def tracker_params
|
28
|
+
{
|
29
|
+
:info_hash => @sha,
|
30
|
+
:peer_id => PEER_ID,
|
31
|
+
:left => @piece_length,
|
32
|
+
:pieces => @file_list
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def connected?
|
37
|
+
@response
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -4,7 +4,6 @@ module Torrenter
|
|
4
4
|
# these methods get mixed in with the Peer class as a way to help
|
5
5
|
# organize and parse the byte-encoded data. The intention is to shorten
|
6
6
|
# and shrink the complexity of the Peer class.
|
7
|
-
|
8
7
|
# the following methods are responsible solely for data retrieval and data transmission
|
9
8
|
|
10
9
|
def send_data(msg, opts={})
|
@@ -1,15 +1,15 @@
|
|
1
|
-
|
2
|
-
require 'digest/sha1'
|
3
|
-
require 'bencode'
|
4
|
-
require 'pry'
|
5
|
-
require 'fileutils'
|
1
|
+
|
6
2
|
|
7
3
|
|
8
4
|
module Torrenter
|
9
5
|
class TorrentReader
|
10
6
|
attr_reader :stream
|
11
7
|
def initialize(stream)
|
12
|
-
@stream
|
8
|
+
@stream = stream
|
9
|
+
@file_list = stream['info']['files'] || stream['info']
|
10
|
+
@sha = Digest::SHA1.digest(stream['info'].bencode)
|
11
|
+
@piece_length = stream['info']['piece length']
|
12
|
+
@sha_list = stream['info']['pieces']
|
13
13
|
end
|
14
14
|
|
15
15
|
def determine_protocol
|
@@ -18,115 +18,51 @@ module Torrenter
|
|
18
18
|
else
|
19
19
|
[@stream['announce']]
|
20
20
|
end
|
21
|
-
|
22
|
-
@http_trackers = trackers.select { |tracker| tracker =~ /http\:\/\// }
|
23
|
-
@udp_trackers = trackers.select { |tracker| tracker =~ /udp\:\/\// }
|
24
|
-
# first try the http trackers...
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
peer_list
|
29
|
-
establish_reactor
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
def connect_to_http_tracker
|
34
|
-
@uri = URI(@http_trackers.shift)
|
35
|
-
@uri.query = URI.encode_www_form(peer_hash)
|
36
|
-
@peers = raw_peers
|
37
|
-
end
|
38
|
-
|
39
|
-
def connect_to_udp_tracker
|
40
|
-
@udp_trackers.map! do |udp|
|
41
|
-
port = udp[/\d+/]
|
42
|
-
udp.gsub!(/udp\:\/\/|\:\d+|\/announce/, '')
|
43
|
-
begin
|
44
|
-
ip = Socket.getaddrinfo(udp, 80)[0][3]
|
45
|
-
udp_socket = UDPConnection.new(ip, port.to_i, sha)
|
46
|
-
udp_socket.connect_to_udp_host
|
47
|
-
rescue SocketError
|
48
|
-
puts 'Unuseable UDP site'
|
49
|
-
end
|
50
|
-
end
|
22
|
+
trackers.each do |track|
|
23
|
+
tracker = build_tracker(track)
|
51
24
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
rescue
|
25
|
+
tracker.connect
|
26
|
+
if tracker.connected?
|
27
|
+
@peers = tracker.address_list
|
28
|
+
break
|
57
29
|
end
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
# url gets reformatted to include query parameters
|
64
|
-
|
65
|
-
def raw_peers
|
66
|
-
begin
|
67
|
-
BEncode.load(Net::HTTP.get(@uri))['peers']
|
68
|
-
rescue
|
69
|
-
nil
|
70
30
|
end
|
71
|
-
end
|
72
|
-
|
73
31
|
|
74
|
-
|
75
|
-
stream['info']['piece length']
|
32
|
+
establish_reactor if @peers
|
76
33
|
end
|
77
34
|
|
78
|
-
def
|
79
|
-
Digest::SHA1.digest(stream['info'].bencode)
|
80
|
-
end
|
81
|
-
|
82
|
-
# data stored as a hash in the order made necessary
|
83
|
-
|
84
|
-
def peer_hash
|
35
|
+
def peer_info
|
85
36
|
{
|
86
|
-
:info_hash
|
87
|
-
:
|
88
|
-
:
|
89
|
-
:
|
37
|
+
:info_hash => @sha,
|
38
|
+
:piece_length => @piece_length,
|
39
|
+
:sha_list => @sha_list,
|
40
|
+
:piece_index => Array.new(@sha_list.size) { false }
|
90
41
|
}
|
91
42
|
end
|
92
43
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
ip_list << { ip: @peers.slice!(0..3).bytes.join('.'), port: @peers.slice!(0..1).unpack("S>").first }
|
44
|
+
def parse_addresses(addr, size)
|
45
|
+
Array.new(size) do
|
46
|
+
peer = { :ip => addr.slice!(0..3).bytes.join('.'),
|
47
|
+
:port => port(addr.slice!(0..1)) }
|
48
|
+
Peer.new(peer, @file_list, peer_info)
|
99
49
|
end
|
100
|
-
|
101
|
-
@peers = ip_list.map { |peer| Peer.new(peer, file_list, peer_info) }
|
102
50
|
end
|
103
51
|
|
104
|
-
def
|
105
|
-
|
106
|
-
list = []
|
107
|
-
until stream['info']['pieces'].bytesize < e
|
108
|
-
list << stream['info']['pieces'].byteslice(n...e)
|
109
|
-
n += 20
|
110
|
-
e += 20
|
111
|
-
end
|
112
|
-
list
|
52
|
+
def port(addr)
|
53
|
+
addr.unpack("S>").first
|
113
54
|
end
|
114
55
|
|
115
|
-
def
|
116
|
-
|
117
|
-
:info_hash => sha,
|
118
|
-
:piece_length => piece_length,
|
119
|
-
:sha_list => sha_list,
|
120
|
-
:piece_index => Array.new(sha_list.size) { false }
|
121
|
-
}
|
56
|
+
def build_tracker(track)
|
57
|
+
track.include?('http://') ? HTTPTracker.new(track, stream) : UDPTracker.new(track, stream)
|
122
58
|
end
|
123
59
|
|
124
|
-
def
|
125
|
-
|
60
|
+
def sha_pieces
|
61
|
+
Array.new(@sha_list.bytesize / 20) { @sha_list.slice!(0..19) }
|
126
62
|
end
|
127
63
|
|
128
64
|
def establish_reactor
|
129
|
-
react = Reactor.new(@peers, sha_list, piece_length, file_list)
|
65
|
+
react = Reactor.new(@peers, @sha_list, @piece_length, @file_list)
|
130
66
|
begin
|
131
67
|
react.message_reactor
|
132
68
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Torrenter
|
2
|
+
class UDPTracker < TorrentReader
|
3
|
+
attr_reader :socket, :response
|
4
|
+
|
5
|
+
def initialize(tracker, stream)
|
6
|
+
@url = tracker[/(?<=udp\:\/\/).+(?=\:\d+)/]
|
7
|
+
@port = tracker[/\d+$/].to_i
|
8
|
+
@socket = UDPSocket.new
|
9
|
+
@connection_id = [0x41727101980].pack("Q>")
|
10
|
+
super(stream)
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect
|
14
|
+
begin
|
15
|
+
@transaction_id = [rand(1000)].pack("I>")
|
16
|
+
@socket.connect(ip_addr, @port)
|
17
|
+
send_msg(connect_msg)
|
18
|
+
read_response
|
19
|
+
rescue
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def send_msg(msg)
|
25
|
+
begin
|
26
|
+
@socket.send(msg, 0)
|
27
|
+
rescue
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def connected?
|
33
|
+
@response
|
34
|
+
end
|
35
|
+
|
36
|
+
def ip_addr
|
37
|
+
Socket.getaddrinfo(@url, @port)[0][3]
|
38
|
+
end
|
39
|
+
|
40
|
+
def connect_to_udp_host
|
41
|
+
begin
|
42
|
+
@socket.connect(@ip, @port)
|
43
|
+
return self
|
44
|
+
rescue
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def address_list
|
50
|
+
|
51
|
+
@connection_id = @response[-8..-1]
|
52
|
+
@transaction_id = [rand(10000)].pack("I>")
|
53
|
+
send_msg(announce_msg)
|
54
|
+
|
55
|
+
read_response
|
56
|
+
|
57
|
+
parse_announce if @response[0..3] == action(1)
|
58
|
+
parse_addresses(@response, @response.size)
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_announce
|
62
|
+
if @response[4..7] == @transaction_id
|
63
|
+
res = @response.slice!(0..11)
|
64
|
+
leechers = @response.slice!(0..3).unpack("I>").first
|
65
|
+
seeders = @response.slice!(0..3).unpack("I>").first
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def send_message
|
70
|
+
begin
|
71
|
+
@socket.send(@msg, 0)
|
72
|
+
rescue
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_response
|
77
|
+
begin
|
78
|
+
@response = @socket.recv(1028)
|
79
|
+
rescue Exception => e
|
80
|
+
e
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def connect_match?
|
85
|
+
data[0] == (action(0) + @transaction_id + @connection_id)
|
86
|
+
end
|
87
|
+
|
88
|
+
def announce_input
|
89
|
+
@connection_id + action(1) + @transaction_id + @sha + PEER_ID
|
90
|
+
end
|
91
|
+
|
92
|
+
def connect_msg
|
93
|
+
@connection_id + action(0) + @transaction_id
|
94
|
+
end
|
95
|
+
|
96
|
+
def action(n)
|
97
|
+
[n].pack("I>")
|
98
|
+
end
|
99
|
+
|
100
|
+
def announce_msg
|
101
|
+
@connection_id + action(1) + @transaction_id + @sha + PEER_ID + [0].pack("Q>") + [0].pack("Q>") + [0].pack("Q>") + action(0) + action(0) + action(0) + action(-1) + [@socket.addr[1]].pack(">S")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
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.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- wismer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: BitTorrent Client written in Ruby
|
14
14
|
email:
|
@@ -20,12 +20,13 @@ extra_rdoc_files: []
|
|
20
20
|
files:
|
21
21
|
- bin/torrenter
|
22
22
|
- lib/torrenter.rb
|
23
|
+
- lib/torrenter/http_tracker.rb
|
23
24
|
- lib/torrenter/message/message_types.rb
|
24
25
|
- lib/torrenter/message/messager.rb
|
25
26
|
- lib/torrenter/peer.rb
|
26
27
|
- lib/torrenter/reactor.rb
|
27
28
|
- lib/torrenter/torrent_reader.rb
|
28
|
-
- lib/torrenter/
|
29
|
+
- lib/torrenter/udp_tracker.rb
|
29
30
|
- lib/torrenter/version.rb
|
30
31
|
homepage: http://wismer.github.io
|
31
32
|
licenses:
|
data/lib/torrenter/udp.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
module Torrenter
|
2
|
-
class UDPConnection
|
3
|
-
attr_reader :sender, :response
|
4
|
-
def initialize(ip, port, info_hash)
|
5
|
-
@ip = ip
|
6
|
-
@port = port
|
7
|
-
@sender = UDPSocket.new
|
8
|
-
@t = rand(10000)
|
9
|
-
@connection_id = [0x41727101980].pack("Q>")
|
10
|
-
@transaction_id = [@t].pack("I>")
|
11
|
-
@info_hash = info_hash
|
12
|
-
end
|
13
|
-
|
14
|
-
def connect_to_udp_host
|
15
|
-
begin
|
16
|
-
@sender.connect(@ip, @port)
|
17
|
-
return self
|
18
|
-
rescue
|
19
|
-
false
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def message_relay
|
24
|
-
@sender.send(connect_msg, 0)
|
25
|
-
|
26
|
-
read_response
|
27
|
-
|
28
|
-
if @response
|
29
|
-
@connection_id = @response[-8..-1]
|
30
|
-
@transaction_id = [rand(10000)].pack("I>")
|
31
|
-
@sender.send(announce_msg, 0)
|
32
|
-
end
|
33
|
-
|
34
|
-
read_response
|
35
|
-
|
36
|
-
if @response
|
37
|
-
parse_announce if @response[0..3] == action(1)
|
38
|
-
return @response
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def parse_announce
|
43
|
-
if @response[4..7] == @transaction_id
|
44
|
-
res = @response.slice!(0..11)
|
45
|
-
leechers = @response.slice!(0..3).unpack("I>").first
|
46
|
-
seeders = @response.slice!(0..3).unpack("I>").first
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def send_message
|
51
|
-
begin
|
52
|
-
@sender.send(@msg, 0)
|
53
|
-
rescue
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def read_response
|
58
|
-
begin
|
59
|
-
@response = @sender.recv(1028)
|
60
|
-
rescue Exception => e
|
61
|
-
e
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def connect_match?
|
66
|
-
data[0] == (action(0) + @transaction_id + @connection_id)
|
67
|
-
end
|
68
|
-
|
69
|
-
def announce_input
|
70
|
-
@connection_id + action(1) + @transaction_id + @info_hash + PEER_ID
|
71
|
-
end
|
72
|
-
|
73
|
-
def connect_msg
|
74
|
-
@connection_id + action(0) + @transaction_id
|
75
|
-
end
|
76
|
-
|
77
|
-
def port
|
78
|
-
@sender.addr[1]
|
79
|
-
end
|
80
|
-
|
81
|
-
def action(n)
|
82
|
-
[n].pack("I>")
|
83
|
-
end
|
84
|
-
|
85
|
-
def announce_msg
|
86
|
-
@connection_id + action(1) + @transaction_id + @info_hash + PEER_ID + [0].pack("Q>") + [0].pack("Q>") + [0].pack("Q>") + action(0) + action(0) + action(0) + action(-1) + [port].pack(">S")
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|