distribustream 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ #--
2
+ # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
+ # All rights reserved. See COPYING for permissions.
4
+ #
5
+ # This source file is distributed as part of the
6
+ # DistribuStream file transfer system.
7
+ #
8
+ # See http://distribustream.rubyforge.org/
9
+ #++
10
+
11
+ require 'rubygems'
12
+ require 'eventmachine'
13
+
14
+ module PDTP
15
+ # EventMachine connection adapter for sending and receiving frames with 16-bit length prefixes
16
+ class Packet < EventMachine::Connection
17
+ # Class for processing length prefixes in packet frames
18
+ class Prefix
19
+ attr_reader :length
20
+
21
+ def initialize
22
+ # Constant at 2 bytes, but expandable if we desire
23
+ @length = 2
24
+ reset!
25
+ end
26
+
27
+ def read?
28
+ @length == @read
29
+ end
30
+
31
+ def append(data)
32
+ toread = @length - @read
33
+ new_data = data[0..(toread - 1)]
34
+
35
+ @data << new_data
36
+ @read += new_data.size
37
+ return nil unless @read == @length
38
+
39
+ @size = @data.unpack('n').first
40
+ result = data[toread..data.size]
41
+
42
+ return nil if result.nil? or result.empty?
43
+ result
44
+ end
45
+
46
+ def size
47
+ return false unless read?
48
+ @size
49
+ end
50
+
51
+ def reset!
52
+ @size = nil
53
+ @read = 0
54
+ @data = ''
55
+ end
56
+ end
57
+
58
+ def initialize(*args)
59
+ super
60
+ @prefix = Prefix.new
61
+ @buffer = ''
62
+ end
63
+
64
+ # Callback for processing incoming frames
65
+ def receive_data(data)
66
+ # Read data and append it to the length prefix unless it's already been read
67
+ data = @prefix.append(data) unless @prefix.read?
68
+ return if data.nil?
69
+
70
+ # If we've read the prefix, append the data
71
+ @buffer << data
72
+
73
+ # Don't do anything until we receive the specified amount of data
74
+ return unless @buffer.size >= @prefix.size
75
+
76
+ # Extract the specified amount of data and process it
77
+ data = @buffer[0..(@prefix.size - 1)]
78
+ receive_packet data
79
+
80
+ # Store any remaining data
81
+ remainder = @buffer[@prefix.size..@buffer.size]
82
+
83
+ # Reset the prefix and buffer since we've received a whole frame
84
+ @prefix.reset!
85
+ @buffer = ''
86
+
87
+ # Don't do anything if there's no more data
88
+ return if remainder.nil? or remainder.empty?
89
+
90
+ # Otherwise continue processing the data
91
+ receive_data remainder
92
+ end
93
+
94
+ # Stub for receiving a frame
95
+ def receive_packet(packet)
96
+ end
97
+
98
+ # Send a packet with a specified length prefix
99
+ def send_packet(data)
100
+ length = data.size
101
+ raise ArgumentError, 'oversize packet' if length >= 256**@prefix.length
102
+
103
+ send_data [length].pack('n') << data
104
+ end
105
+ end
106
+ end
@@ -11,7 +11,6 @@
11
11
  require 'rubygems'
12
12
  require 'eventmachine'
13
13
  require 'thread'
14
- require 'uri'
15
14
  require 'ipaddr'
16
15
 
17
16
  begin
@@ -20,6 +19,8 @@ rescue LoadError
20
19
  require 'json'
21
20
  end
22
21
 
22
+ require File.dirname(__FILE__) + '/packet.rb'
23
+
23
24
  module PDTP
24
25
  PROTOCOL_DEBUG=true
25
26
 
@@ -30,9 +31,8 @@ module PDTP
30
31
  end
31
32
 
32
33
  # EventMachine handler class for the PDTP protocol
33
- class Protocol < EventMachine::Protocols::LineAndTextProtocol
34
+ class Protocol < PDTP::Packet
34
35
  @@num_connections = 0
35
- @@listener = nil
36
36
  @@message_params = nil
37
37
  @connection_open = false
38
38
 
@@ -40,15 +40,10 @@ module PDTP
40
40
  @connection_open
41
41
  end
42
42
 
43
- #sets the listener class (Server or Client)
44
- def self.listener=(listener)
45
- @@listener = listener
46
- end
47
-
48
- def initialize(*args)
43
+ def initialize(data)
49
44
  user_data = nil
50
45
  @mutex = Mutex.new
51
- super
46
+ super data
52
47
  end
53
48
 
54
49
  #called by EventMachine after a connection has been established
@@ -64,7 +59,7 @@ module PDTP
64
59
 
65
60
  @@num_connections += 1
66
61
  @connection_open = true
67
- @@listener.connection_created(self) if @@listener.respond_to?(:connection_created)
62
+ connection_created if respond_to? :connection_created
68
63
  end
69
64
 
70
65
  attr_accessor :user_data #users of this class may store arbitrary data here
@@ -79,30 +74,25 @@ module PDTP
79
74
  end
80
75
  end
81
76
 
82
- #override this in a child class to handle messages
83
- def receive_message(command, message)
84
- @@listener.dispatch_message command, message, self
85
- end
86
-
87
77
  #debug routine: returns id of remote peer on this connection
88
78
  def remote_peer_id
89
79
  ret = user_data.client_id rescue nil
90
80
  ret || 'NOID'
91
81
  end
92
82
 
93
- #called for each line of text received over the wire
83
+ #called for each packet of data received over the wire
94
84
  #parses the JSON message and dispatches the message
95
- def receive_line line
85
+ def receive_packet(packet)
96
86
  begin
97
- line.chomp!
98
- @@log.debug "(#{remote_peer_id}) recv: " + line
99
- message = JSON.parse(line) rescue nil
100
- raise ProtocolError.new("JSON couldn't parse: #{line}") if message.nil?
87
+ packet.chomp!
88
+ @@log.debug "(#{remote_peer_id}) recv: " + packet
89
+ message = JSON.parse(packet) rescue nil
90
+ raise ProtocolError.new("JSON couldn't parse: #{packet}") if message.nil?
101
91
  Protocol.validate_message message
102
92
 
103
93
  command, options = message
104
94
  hash_to_range command, options
105
- receive_message command, options
95
+ receive_message(command, options) if respond_to? :receive_message
106
96
  rescue ProtocolError => e
107
97
  @@log.warn "(#{remote_peer_id}) PROTOCOL ERROR: #{e.to_s}"
108
98
  @@log.debug e.backtrace.join("\n")
@@ -166,17 +156,17 @@ module PDTP
166
156
  # Second entry is an options hash/object
167
157
  message = [command.to_s, opts]
168
158
 
169
- @mutex.synchronize do
159
+ #@mutex.synchronize do
170
160
  outstr = JSON.unparse(message) + "\n"
171
161
  @@log.debug "(#{remote_peer_id}) send: #{outstr.chomp}"
172
- send_data outstr
173
- end
162
+ send_packet outstr
163
+ #end
174
164
  end
175
165
 
176
166
  #called by EventMachine when a connection is closed
177
167
  def unbind
178
168
  @@num_connections -= 1
179
- @@listener.connection_destroyed(self) if @@listener.respond_to?(:connection_destroyed)
169
+ connection_destroyed if respond_to? :connection_destroyed
180
170
  @connection_open = false
181
171
  end
182
172
 
@@ -249,7 +239,7 @@ module PDTP
249
239
  mp = {}
250
240
 
251
241
  #must be the first message the client sends
252
- mp["client_info"]={
242
+ mp["register"]={
253
243
  "client_id"=>:string,
254
244
  "listen_port"=>:int
255
245
  }
@@ -277,7 +267,7 @@ module PDTP
277
267
  "url"=>:url,
278
268
  "range"=>:range,
279
269
  "peer_id"=>:string,
280
- "is_authorized"=>:bool
270
+ "authorized"=>:bool
281
271
  }
282
272
 
283
273
  mp["request"]={
@@ -8,133 +8,135 @@
8
8
  # See http://distribustream.rubyforge.org/
9
9
  #++
10
10
 
11
- require File.dirname(__FILE__)+'/trust.rb'
11
+ require File.dirname(__FILE__) + '/trust'
12
12
 
13
13
  module PDTP
14
- #stores information about a single connected client
15
- class ClientInfo
16
- attr_accessor :chunk_info, :trust
17
- attr_accessor :listen_port, :client_id
18
- attr_accessor :transfers
19
-
20
- def initialize
21
- @chunk_info=ChunkInfo.new
22
- @listen_port=6000 #default
23
- @trust=Trust.new
24
- @transfers=Hash.new
25
- end
14
+ class Server
15
+ #stores information about a single connected client
16
+ class ClientInfo
17
+ attr_accessor :chunk_info, :trust
18
+ attr_accessor :listen_port, :client_id
19
+ attr_accessor :transfers
26
20
 
27
- # returns true if this client wants the server to spawn a transfer for it
28
- def wants_download?
29
- transfer_state_allowed=5
30
- total_allowed=10
31
- transferring=0
32
- @transfers.each do |key, t|
33
- transferring=transferring+1 if t.verification_asked
34
- return false if transferring >= transfer_state_allowed
21
+ def initialize
22
+ @chunk_info=ChunkInfo.new
23
+ @listen_port=6000 #default
24
+ @trust=Trust.new
25
+ @transfers=Hash.new
35
26
  end
36
27
 
37
- @transfers.size < total_allowed
38
- end
39
-
40
- #this could have a different definition, but it works fine to use wants_download?
41
- alias_method :wants_upload?, :wants_download?
42
-
43
- #returns a list of all the stalled transfers this client is a part of
44
- def get_stalled_transfers
45
- stalled=[]
46
- timeout=20.0
47
- now=Time.now
48
- @transfers.each do |key,t|
49
- #only delete if we are the acceptor to prevent race conditions
50
- next if t.acceptor.user_data != self
51
- if now-t.creation_time > timeout and not t.verification_asked
52
- stalled << t
28
+ # returns true if this client wants the server to spawn a transfer for it
29
+ def wants_download?
30
+ transfer_state_allowed=5
31
+ total_allowed=10
32
+ transferring=0
33
+ @transfers.each do |key, t|
34
+ transferring=transferring+1 if t.verification_asked
35
+ return false if transferring >= transfer_state_allowed
53
36
  end
54
- end
55
- stalled
56
- end
57
- end
58
37
 
59
- #stores information about the chunks requested or provided by a client
60
- class ChunkInfo
61
- def initialize
62
- @files={}
63
- end
38
+ @transfers.size < total_allowed
39
+ end
64
40
 
65
- #each chunk can either be provided, requested, transfer, or none
66
- def provide(filename,range); set(filename,range,:provided) ; end
67
- def unprovide(filename,range); set(filename,range, :none); end
68
- def request(filename,range); set(filename,range, :requested); end
69
- def unrequest(filename,range); set(filename,range, :none); end
70
- def transfer(filename,range); set(filename,range, :transfer); end
71
-
72
- def provided?(filename,chunk); get(filename,chunk) == :provided; end
73
- def requested?(filename,chunk); get(filename,chunk) == :requested; end
74
-
75
- #returns a high priority requested chunk
76
- def high_priority_chunk
77
- #right now return any chunk
78
- @files.each do |name,file|
79
- file.each_index do |i|
80
- return [name,i] if file[i]==:requested
81
- end
82
- end
83
-
84
- nil
85
- end
41
+ #this could have a different definition, but it works fine to use wants_download?
42
+ alias_method :wants_upload?, :wants_download?
86
43
 
87
- #calls a block for each chunk of the specified type
88
- def each_chunk_of_type(type)
89
- @files.each do |name,file|
90
- file.each_index do |i|
91
- yield(name,i) if file[i]==type
44
+ #returns a list of all the stalled transfers this client is a part of
45
+ def get_stalled_transfers
46
+ stalled=[]
47
+ timeout=20.0
48
+ now=Time.now
49
+ @transfers.each do |key,t|
50
+ #only delete if we are the acceptor to prevent race conditions
51
+ next if t.acceptor.user_data != self
52
+ if now-t.creation_time > timeout and not t.verification_asked
53
+ stalled << t
54
+ end
92
55
  end
93
- end
56
+ stalled
57
+ end
94
58
  end
95
59
 
96
- class FileStats
97
- attr_accessor :file_chunks, :chunks_requested,:url
98
- attr_accessor :chunks_provided, :chunks_transferring
99
-
60
+ #stores information about the chunks requested or provided by a client
61
+ class ChunkInfo
100
62
  def initialize
101
- @url=""
102
- @file_chunks=0
103
- @chunks_requested=0
104
- @chunks_provided=0
105
- @chunks_transferring=0
63
+ @files={}
106
64
  end
107
- end
108
65
 
109
- #returns an array of FileStats objects for debug output
110
- def get_file_stats
111
- stats=[]
112
- @files.each do |name,file|
113
- fs=FileStats.new
114
- fs.file_chunks=file.size
115
- fs.url=name
116
- file.each do |chunk|
117
- fs.chunks_requested+=1 if chunk==:requested
118
- fs.chunks_provided+=1 if chunk==:provided
119
- fs.chunks_transferring+=1 if chunk==:transfer
66
+ #each chunk can either be provided, requested, transfer, or none
67
+ def provide(filename,range); set(filename,range,:provided) ; end
68
+ def unprovide(filename,range); set(filename,range, :none); end
69
+ def request(filename,range); set(filename,range, :requested); end
70
+ def unrequest(filename,range); set(filename,range, :none); end
71
+ def transfer(filename,range); set(filename,range, :transfer); end
72
+
73
+ def provided?(filename,chunk); get(filename,chunk) == :provided; end
74
+ def requested?(filename,chunk); get(filename,chunk) == :requested; end
75
+
76
+ #returns a high priority requested chunk
77
+ def high_priority_chunk
78
+ #right now return any chunk
79
+ @files.each do |name,file|
80
+ file.each_index do |i|
81
+ return [name,i] if file[i]==:requested
82
+ end
83
+ end
84
+
85
+ nil
86
+ end
87
+
88
+ #calls a block for each chunk of the specified type
89
+ def each_chunk_of_type(type)
90
+ @files.each do |name,file|
91
+ file.each_index do |i|
92
+ yield(name,i) if file[i]==type
93
+ end
94
+ end
95
+ end
96
+
97
+ class FileStats
98
+ attr_accessor :file_chunks, :chunks_requested,:url
99
+ attr_accessor :chunks_provided, :chunks_transferring
100
+
101
+ def initialize
102
+ @url=""
103
+ @file_chunks=0
104
+ @chunks_requested=0
105
+ @chunks_provided=0
106
+ @chunks_transferring=0
120
107
  end
121
- stats << fs
122
108
  end
123
-
124
- stats
125
- end
126
109
 
127
- #########
128
- protected
129
- #########
110
+ #returns an array of FileStats objects for debug output
111
+ def get_file_stats
112
+ stats=[]
113
+ @files.each do |name,file|
114
+ fs=FileStats.new
115
+ fs.file_chunks=file.size
116
+ fs.url=name
117
+ file.each do |chunk|
118
+ fs.chunks_requested+=1 if chunk==:requested
119
+ fs.chunks_provided+=1 if chunk==:provided
120
+ fs.chunks_transferring+=1 if chunk==:transfer
121
+ end
122
+ stats << fs
123
+ end
130
124
 
131
- def get(filename,chunk)
132
- @files[filename][chunk] rescue :neither
133
- end
125
+ stats
126
+ end
127
+
128
+ #########
129
+ protected
130
+ #########
134
131
 
135
- def set(filename,range,state)
136
- chunks=@files[filename]||=Array.new
137
- range.each { |i| chunks[i]=state }
132
+ def get(filename,chunk)
133
+ @files[filename][chunk] rescue :neither
134
+ end
135
+
136
+ def set(filename,range,state)
137
+ chunks=@files[filename]||=Array.new
138
+ range.each { |i| chunks[i]=state }
139
+ end
138
140
  end
139
141
  end
140
142
  end
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
+ # All rights reserved. See COPYING for permissions.
4
+ #
5
+ # This source file is distributed as part of the
6
+ # DistribuStream file transfer system.
7
+ #
8
+ # See http://distribustream.rubyforge.org/
9
+ #++
10
+
11
+ require File.dirname(__FILE__) + '/../common/protocol'
12
+
13
+ module PDTP
14
+ class Server
15
+ class Connection < PDTP::Protocol
16
+ attr_accessor :server
17
+ attr_accessor :user_data
18
+
19
+ def connection_completed
20
+ raise(RuntimeError, 'server was never initialized') unless @server
21
+ @server.connection_created self
22
+ end
23
+
24
+ def connection_destroyed
25
+ raise(RuntimeError, 'server was never initialized') unless @server
26
+ @server.connection_destroyed self
27
+ end
28
+
29
+ def receive_message(command, message)
30
+ raise(RuntimeError, 'server was never initialized') unless @server
31
+ @server.dispatch_message command, message, self
32
+ end
33
+ end
34
+ end
35
+ end