torrenter 0.0.3 → 0.0.4
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/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
|