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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f2ef8e3760de9d724d24826b929e2d40bcbea4be
4
- data.tar.gz: 58aa8774b9ca139ec06d6c44751b48745f339147
3
+ metadata.gz: 992e0e356ed722f0cb7131a5029b454e6fdba9e2
4
+ data.tar.gz: 64ae0a3e57481d38ac75668773303d2ab601fa91
5
5
  SHA512:
6
- metadata.gz: ce174384bf065513a2b487098c7430d195280c87d1ed0c9a3b1205b768b0effe4339c419710a7c99477780b6a6be12e4bafac6d41c4950393124082ade4002e6
7
- data.tar.gz: 8985f1f8769bccb328fefbb122f7d024537c9958fdd6d07e89041935291c89f63f61fc487c8dc8237ee15751fb0789441b497ad435131b133fb2f64850bea53e
6
+ metadata.gz: bb19b53d0d740111e96941af9a02a07352979196ead79e1c34c796f42fdcec5d1aab7d6527b309f8cea84dec1513cc6b76b680f02c562f14c8993b7fa7a0353e
7
+ data.tar.gz: a2870db5b56bfc2a887a2530ea924671fa677e0fb26ce0b80311148bf83a29b9457a2b8282e0ec9de832bfa8b401cecd9479eacac3441dd36f7ae342072e9273
@@ -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
- require 'socket'
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 = 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
- connect_to_http_tracker if @http_trackers.size > 0
27
- connect_to_udp_tracker if @peers.nil?
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
- while @peers.nil?
53
- udp = @udp_trackers.shift
54
- begin
55
- @peers = udp.message_relay
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
- def piece_length
75
- stream['info']['piece length']
32
+ establish_reactor if @peers
76
33
  end
77
34
 
78
- def sha
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 => sha,
87
- :peer_id => PEER_ID,
88
- :left => piece_length,
89
- :pieces => stream['info']['files'] || stream['info']['name']
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
- # Using the peers key of the torrent file, the hex-encoded data gets reinterpreted as ips addresses.
94
-
95
- def peer_list
96
- ip_list = []
97
- until @peers.empty?
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 sha_list
105
- n, e = 0, 20
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 peer_info
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 file_list
125
- stream['info']['files'] || stream['info']
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
@@ -1,3 +1,3 @@
1
1
  module Torrenter
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
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.3
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-25 00:00:00.000000000 Z
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/udp.rb
29
+ - lib/torrenter/udp_tracker.rb
29
30
  - lib/torrenter/version.rb
30
31
  homepage: http://wismer.github.io
31
32
  licenses:
@@ -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