distribustream 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +2 -0
- data/COPYING +674 -0
- data/README +107 -0
- data/Rakefile +44 -0
- data/bin/distribustream +60 -0
- data/bin/dsclient +43 -0
- data/bin/dsseed +103 -0
- data/conf/bigchunk.yml +18 -0
- data/conf/debug.yml +18 -0
- data/conf/example.yml +18 -0
- data/distribustream.gemspec +20 -0
- data/lib/pdtp/client.rb +195 -0
- data/lib/pdtp/client/file_buffer.rb +128 -0
- data/lib/pdtp/client/file_buffer_spec.rb +154 -0
- data/lib/pdtp/client/file_service.rb +60 -0
- data/lib/pdtp/client/protocol.rb +66 -0
- data/lib/pdtp/client/transfer.rb +229 -0
- data/lib/pdtp/common/common_init.rb +122 -0
- data/lib/pdtp/common/file_service.rb +69 -0
- data/lib/pdtp/common/file_service_spec.rb +91 -0
- data/lib/pdtp/common/protocol.rb +346 -0
- data/lib/pdtp/common/protocol_spec.rb +68 -0
- data/lib/pdtp/server.rb +368 -0
- data/lib/pdtp/server/client_info.rb +140 -0
- data/lib/pdtp/server/file_service.rb +89 -0
- data/lib/pdtp/server/transfer.rb +62 -0
- data/lib/pdtp/server/trust.rb +88 -0
- data/lib/pdtp/server/trust_spec.rb +40 -0
- metadata +114 -0
@@ -0,0 +1,122 @@
|
|
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
|
+
#Provides functions used for initialization by both the client and server
|
12
|
+
|
13
|
+
require 'optparse'
|
14
|
+
require 'logger'
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) + '/protocol'
|
17
|
+
|
18
|
+
STDOUT.sync=true
|
19
|
+
STDERR.sync=true
|
20
|
+
|
21
|
+
@@log=Logger.new(STDOUT)
|
22
|
+
@@log.datetime_format=""
|
23
|
+
|
24
|
+
CONFIG_TYPES = {
|
25
|
+
:host => :string,
|
26
|
+
:vhost => :string,
|
27
|
+
:port => :int,
|
28
|
+
:listen_port => :int,
|
29
|
+
:file_root => :string,
|
30
|
+
:quiet => :bool,
|
31
|
+
:chunk_size => :int,
|
32
|
+
:request_url => :string
|
33
|
+
}
|
34
|
+
|
35
|
+
#prints banner and loads config file
|
36
|
+
def common_init(program_name, config = nil)
|
37
|
+
@@config = config || {
|
38
|
+
:host => '0.0.0.0',
|
39
|
+
:port => 6086, #server port
|
40
|
+
:listen_port => 8000, #client listen port
|
41
|
+
:file_root => '.',
|
42
|
+
:chunk_size => 5000,
|
43
|
+
:quiet => true
|
44
|
+
}
|
45
|
+
|
46
|
+
config_filename=nil
|
47
|
+
|
48
|
+
unless config
|
49
|
+
OptionParser.new do |opts|
|
50
|
+
opts.banner = "Usage: #{program_name} [options]"
|
51
|
+
opts.on("--config CONFIGFILE", "Load specified config file.") do |c|
|
52
|
+
config_filename=c
|
53
|
+
end
|
54
|
+
opts.on("--help", "Prints this usage info.") do
|
55
|
+
puts opts
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end.parse!
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "#{program_name} starting. Run '#{program_name} --help' for more info."
|
62
|
+
|
63
|
+
load_config_file(config_filename) unless config
|
64
|
+
|
65
|
+
begin
|
66
|
+
@@config[:file_root]=File.expand_path(@@config[:file_root])
|
67
|
+
rescue
|
68
|
+
puts "Invalid path specified for file_root"
|
69
|
+
return
|
70
|
+
end
|
71
|
+
|
72
|
+
puts "@@config=#{@@config.inspect}"
|
73
|
+
validate_config_options
|
74
|
+
handle_config_options
|
75
|
+
end
|
76
|
+
|
77
|
+
#loads a config file specified by config_filename
|
78
|
+
def load_config_file(config_filename)
|
79
|
+
if config_filename.nil?
|
80
|
+
puts "No config file specified. Using defaults."
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
confstr=File.read(config_filename) rescue nil
|
85
|
+
if confstr.nil?
|
86
|
+
puts "Unable to open config file: #{config_filename}"
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
new_config = YAML.load confstr
|
92
|
+
@@config.merge!(new_config)
|
93
|
+
@@config[:vhost] ||= @@config[:host] # Use host as vhost unless specified
|
94
|
+
rescue Exception => e
|
95
|
+
puts "Error parsing config file: #{config_filename}"
|
96
|
+
puts e
|
97
|
+
exit
|
98
|
+
end
|
99
|
+
|
100
|
+
puts "Loaded config file: #{config_filename}"
|
101
|
+
end
|
102
|
+
|
103
|
+
#make sure all the config options are of the right type
|
104
|
+
def validate_config_options
|
105
|
+
@@config.each do |key,val|
|
106
|
+
type=CONFIG_TYPES[key]
|
107
|
+
if type.nil?
|
108
|
+
puts "Unknown parameter: #{key}"
|
109
|
+
exit
|
110
|
+
end
|
111
|
+
|
112
|
+
unless PDTP::Protocol.obj_matches_type?(val,type)
|
113
|
+
puts "Parameter: #{key} is not of type: #{type}"
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#responds to config options that are used by both client and server
|
120
|
+
def handle_config_options
|
121
|
+
@@log.level=Logger::INFO if @@config[:quiet]
|
122
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
module PDTP
|
12
|
+
#provides information about a single file on the network
|
13
|
+
class FileInfo
|
14
|
+
attr_accessor :file_size, :base_chunk_size, :streaming
|
15
|
+
|
16
|
+
#number of chunks in the file
|
17
|
+
def num_chunks
|
18
|
+
return 0 if @file_size==0
|
19
|
+
(@file_size - 1) / @base_chunk_size + 1
|
20
|
+
end
|
21
|
+
|
22
|
+
#size of the specified chunk
|
23
|
+
def chunk_size(chunkid)
|
24
|
+
raise "Invalid chunkid #{chunkid}" if chunkid<0 or chunkid>=num_chunks
|
25
|
+
chunkid == num_chunks - 1 ? @file_size - @base_chunk_size * chunkid : @base_chunk_size
|
26
|
+
end
|
27
|
+
|
28
|
+
#range of bytes taken up by this chunk in the entire file
|
29
|
+
def chunk_range(chunkid)
|
30
|
+
start_byte = chunkid * @base_chunk_size
|
31
|
+
end_byte = start_byte + chunk_size(chunkid) - 1
|
32
|
+
start_byte..end_byte
|
33
|
+
end
|
34
|
+
|
35
|
+
#returns the chunkid that contains the requested byte offset
|
36
|
+
def chunk_from_offset(offset)
|
37
|
+
raise "Invalid offset #{offset}" if offset < 0 or offset >= @file_size
|
38
|
+
offset / @base_chunk_size
|
39
|
+
end
|
40
|
+
|
41
|
+
#takes a byte_range in the file and returns an equivalent chunk range
|
42
|
+
#if exclude_partial is true, chunks that are not completely covered by the byte range are left out
|
43
|
+
def chunk_range_from_byte_range(byte_range,exclude_partial=true)
|
44
|
+
min=chunk_from_offset(byte_range.first)
|
45
|
+
min+=1 if exclude_partial and byte_range.first > min*@base_chunk_size
|
46
|
+
|
47
|
+
max_byte=byte_range.last
|
48
|
+
max_byte=@file_size-1 if max_byte==-1 or max_byte>=@file_size
|
49
|
+
max=chunk_from_offset(max_byte)
|
50
|
+
max-=1 if exclude_partial and max_byte<chunk_range(max).last
|
51
|
+
min..max
|
52
|
+
end
|
53
|
+
|
54
|
+
#returns a string containing the data for this chunk
|
55
|
+
#range specifies a range of bytes local to this chunk
|
56
|
+
#implemented in Client and Server file services
|
57
|
+
def chunk_data(chunkid,range=nil)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# base class for ClientFileService and ServerFileService.
|
62
|
+
# provides shared functionality
|
63
|
+
|
64
|
+
class FileService
|
65
|
+
#returns a FileInfo class associated with the url, or nil if the file isnt known
|
66
|
+
def get_info(url)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,91 @@
|
|
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__) + '/file_service'
|
12
|
+
|
13
|
+
describe "A FileInfo with chunk_size=1" do
|
14
|
+
before(:each) do
|
15
|
+
@fi=PDTP::FileInfo.new
|
16
|
+
@fi.file_size=5
|
17
|
+
@fi.base_chunk_size=1
|
18
|
+
end
|
19
|
+
|
20
|
+
it "chunk_size works" do
|
21
|
+
@fi.chunk_size(0).should == 1
|
22
|
+
@fi.chunk_size(3).should == 1
|
23
|
+
@fi.chunk_size(4).should == 1
|
24
|
+
|
25
|
+
proc{ @fi.chunk_size(-1)}.should raise_error
|
26
|
+
proc{ @fi.chunk_size(5)}.should raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it "num_chunks works" do
|
30
|
+
@fi.num_chunks.should == 5
|
31
|
+
end
|
32
|
+
|
33
|
+
it "chunk_from_offset works" do
|
34
|
+
@fi.chunk_from_offset(0).should == 0
|
35
|
+
@fi.chunk_from_offset(4).should == 4
|
36
|
+
proc{@fi.chunk_from_offset(5)}.should raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it "chunk_range_from_byte_range works" do
|
40
|
+
@fi.chunk_range_from_byte_range(0..4,false).should == (0..4)
|
41
|
+
@fi.chunk_range_from_byte_range(0..4,true).should == (0..4)
|
42
|
+
proc{@fi.chunk_range_from_byte_range(-1..3,true)}.should raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "A FileInfo with chunk_size=256 and file_size=768" do
|
48
|
+
before(:each) do
|
49
|
+
@fi=PDTP::FileInfo.new
|
50
|
+
@fi.base_chunk_size=256
|
51
|
+
@fi.file_size=768
|
52
|
+
end
|
53
|
+
|
54
|
+
it "chunk_size works" do
|
55
|
+
@fi.chunk_size(0).should == 256
|
56
|
+
@fi.chunk_size(2).should == 256
|
57
|
+
proc{@fi.chunk_size(3)}.should raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it "num_chunks works" do
|
61
|
+
@fi.num_chunks.should == 3
|
62
|
+
end
|
63
|
+
|
64
|
+
it "chunk_from_offset works" do
|
65
|
+
@fi.chunk_from_offset(256).should == 1
|
66
|
+
@fi.chunk_from_offset(255).should == 0
|
67
|
+
end
|
68
|
+
|
69
|
+
it "chunk_range_from_byte_range works" do
|
70
|
+
@fi.chunk_range_from_byte_range(256..511,true).should == (1..1)
|
71
|
+
@fi.chunk_range_from_byte_range(256..511,false).should == (1..1)
|
72
|
+
@fi.chunk_range_from_byte_range(255..512,true).should == (1..1)
|
73
|
+
@fi.chunk_range_from_byte_range(255..512,false).should == (0..2)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "A FileInfo with chunk_size=256 and file_size=255" do
|
78
|
+
before(:each) do
|
79
|
+
@fi=PDTP::FileInfo.new
|
80
|
+
@fi.base_chunk_size=256
|
81
|
+
@fi.file_size=255
|
82
|
+
end
|
83
|
+
|
84
|
+
it "num_chunks works" do
|
85
|
+
@fi.num_chunks.should ==1
|
86
|
+
end
|
87
|
+
|
88
|
+
it "chunk_from_offset works" do
|
89
|
+
@fi.chunk_from_offset(254).should == 0
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,346 @@
|
|
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
|
+
require 'thread'
|
14
|
+
require 'uri'
|
15
|
+
require 'ipaddr'
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'fjson'
|
19
|
+
rescue LoadError
|
20
|
+
require 'json'
|
21
|
+
end
|
22
|
+
|
23
|
+
module PDTP
|
24
|
+
PROTOCOL_DEBUG=true
|
25
|
+
|
26
|
+
class ProtocolError < Exception
|
27
|
+
end
|
28
|
+
|
29
|
+
class ProtocolWarn < Exception
|
30
|
+
end
|
31
|
+
|
32
|
+
# EventMachine handler class for the PDTP protocol
|
33
|
+
class Protocol < EventMachine::Protocols::LineAndTextProtocol
|
34
|
+
@@num_connections = 0
|
35
|
+
@@listener = nil
|
36
|
+
@@message_params = nil
|
37
|
+
@connection_open = false
|
38
|
+
|
39
|
+
def connection_open?
|
40
|
+
@connection_open
|
41
|
+
end
|
42
|
+
|
43
|
+
#sets the listener class (Server or Client)
|
44
|
+
def self.listener=(listener)
|
45
|
+
@@listener = listener
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(*args)
|
49
|
+
user_data = nil
|
50
|
+
@mutex = Mutex.new
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
#called by EventMachine after a connection has been established
|
55
|
+
def post_init
|
56
|
+
# a cache of the peer info because eventmachine seems to drop it before we want
|
57
|
+
peername = get_peername
|
58
|
+
if peername.nil?
|
59
|
+
@cached_peer_info = ["<Peername nil!!!>", 91119] if peername.nil?
|
60
|
+
else
|
61
|
+
port, addr = Socket.unpack_sockaddr_in(peername)
|
62
|
+
@cached_peer_info = [addr.to_s, port.to_i]
|
63
|
+
end
|
64
|
+
|
65
|
+
@@num_connections += 1
|
66
|
+
@connection_open = true
|
67
|
+
@@listener.connection_created(self) if @@listener.respond_to?(:connection_created)
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_accessor :user_data #users of this class may store arbitrary data here
|
71
|
+
|
72
|
+
#close a connection, but first send the specified error message
|
73
|
+
def error_close_connection(error)
|
74
|
+
if PROTOCOL_DEBUG
|
75
|
+
send_message :protocol_error, :message => msg
|
76
|
+
close_connection(true) # close after writing
|
77
|
+
else
|
78
|
+
close_connection
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
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
|
+
#debug routine: returns id of remote peer on this connection
|
88
|
+
def remote_peer_id
|
89
|
+
ret = user_data.client_id rescue nil
|
90
|
+
ret || 'NOID'
|
91
|
+
end
|
92
|
+
|
93
|
+
#called for each line of text received over the wire
|
94
|
+
#parses the JSON message and dispatches the message
|
95
|
+
def receive_line line
|
96
|
+
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?
|
101
|
+
Protocol.validate_message message
|
102
|
+
|
103
|
+
command, options = message
|
104
|
+
hash_to_range command, options
|
105
|
+
receive_message command, options
|
106
|
+
rescue ProtocolError => e
|
107
|
+
@@log.warn "(#{remote_peer_id}) PROTOCOL ERROR: #{e.to_s}"
|
108
|
+
@@log.debug e.backtrace.join("\n")
|
109
|
+
error_close_connection e.to_s
|
110
|
+
rescue ProtocolWarn => e
|
111
|
+
send_message :protocol_warn, :message => e.to_s
|
112
|
+
rescue Exception => e
|
113
|
+
puts "(#{remote_peer_id}) UNKNOWN EXCEPTION #{e.to_s}"
|
114
|
+
puts e.backtrace.join("\n")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
RANGENAMES = %w{chunk_range range byte_range}
|
119
|
+
|
120
|
+
#converts Ruby Range classes in the message to PDTP protocol hashes with min and max
|
121
|
+
# 0..-1 => nil (entire file)
|
122
|
+
# 10..-1 => {"min"=>10} (contents of file >= 10)
|
123
|
+
def range_to_hash(message)
|
124
|
+
message.each do |key,value|
|
125
|
+
if value.class==Range
|
126
|
+
if value==(0..-1)
|
127
|
+
message.delete(key)
|
128
|
+
elsif value.last==-1
|
129
|
+
message[key]={"min"=>value.first}
|
130
|
+
else
|
131
|
+
message[key]={"min"=>value.first,"max"=>value.last}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
#converts a PDTP protocol min and max hash to a Ruby Range class
|
138
|
+
def hash_to_range(command, message)
|
139
|
+
key="range"
|
140
|
+
auto_types=["provide","request"] #these types assume a range if it isnt specified
|
141
|
+
auto_types.each do |type|
|
142
|
+
if command == type and message[key].nil?
|
143
|
+
message[key]={} # assume entire file if not specified
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
if message[key]
|
148
|
+
raise if message[key].class!=Hash
|
149
|
+
min=message[key]["min"]
|
150
|
+
max=message[key]["max"]
|
151
|
+
message[key]= (min ? min : 0)..(max ? max : -1)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#sends a message, in the internal Hash format, over the wire
|
156
|
+
def send_message(command, opts = {})
|
157
|
+
#message = opts.merge(:type => command.to_s)
|
158
|
+
|
159
|
+
# Stringify all option keys
|
160
|
+
opts = opts.map { |k,v| [k.to_s, v] }.inject({}) { |h,(k,v)| h[k] = v; h }
|
161
|
+
|
162
|
+
# Convert all Ruby ranges to JSON objects representing them
|
163
|
+
range_to_hash opts
|
164
|
+
|
165
|
+
# Message format is a JSON array with the command (string) as the first entry
|
166
|
+
# Second entry is an options hash/object
|
167
|
+
message = [command.to_s, opts]
|
168
|
+
|
169
|
+
@mutex.synchronize do
|
170
|
+
outstr = JSON.unparse(message) + "\n"
|
171
|
+
@@log.debug "(#{remote_peer_id}) send: #{outstr.chomp}"
|
172
|
+
send_data outstr
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
#called by EventMachine when a connection is closed
|
177
|
+
def unbind
|
178
|
+
@@num_connections -= 1
|
179
|
+
@@listener.connection_destroyed(self) if @@listener.respond_to?(:connection_destroyed)
|
180
|
+
@connection_open = false
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.print_info
|
184
|
+
puts "num_connections=#{@@num_connections}"
|
185
|
+
end
|
186
|
+
|
187
|
+
#returns the ip address and port in an array [ip, port]
|
188
|
+
def get_peer_info
|
189
|
+
@cached_peer_info
|
190
|
+
end
|
191
|
+
|
192
|
+
def to_s
|
193
|
+
addr,port = get_peer_info
|
194
|
+
"#{addr}:#{port}"
|
195
|
+
end
|
196
|
+
|
197
|
+
#makes sure that the message is valid.
|
198
|
+
#if not, throws a ProtocolError
|
199
|
+
def self.validate_message(message)
|
200
|
+
raise ProtocolError.new("Message is not a JSON array") unless message.is_a? Array
|
201
|
+
command, options = message
|
202
|
+
|
203
|
+
@@message_params ||= define_message_params
|
204
|
+
|
205
|
+
params = @@message_params[command] rescue nil
|
206
|
+
raise ProtocolError.new("Invalid message type: #{command}") if params.nil?
|
207
|
+
|
208
|
+
params.each do |name,type|
|
209
|
+
if type.class == Optional
|
210
|
+
next if options[name].nil? #dont worry about it if they dont have this param
|
211
|
+
type = type.type #grab the real type from within the optional class
|
212
|
+
end
|
213
|
+
|
214
|
+
raise ProtocolError.new("required parameter: '#{name}' missing for message: '#{command}'") if options[name].nil?
|
215
|
+
unless obj_matches_type?(options[name], type)
|
216
|
+
raise ProtocolError.new("parameter: '#{name}' val='#{options[name]}' is not of type: '#{type}' for message: '#{command}' ")
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# an optional field of the specified type
|
222
|
+
class Optional
|
223
|
+
attr_accessor :type
|
224
|
+
def initialize(type)
|
225
|
+
@type=type
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
#returns whether or not a given ruby object matches the specified type
|
230
|
+
#available types:
|
231
|
+
# :url, :range, :ip, :int, :bool, :string
|
232
|
+
def self.obj_matches_type?(obj,type)
|
233
|
+
case type
|
234
|
+
when :url then obj.class == String
|
235
|
+
when :range then obj.class == Range or obj.class == Hash
|
236
|
+
when :int then obj.class == Fixnum
|
237
|
+
when :bool then obj == true or obj == false
|
238
|
+
when :string then obj.class == String
|
239
|
+
when :ip
|
240
|
+
ip = IPAddr.new(obj) rescue nil
|
241
|
+
!ip.nil?
|
242
|
+
else
|
243
|
+
raise "Invalid type specified: #{type}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
#this function defines the required fields for each message
|
248
|
+
def self.define_message_params
|
249
|
+
mp = {}
|
250
|
+
|
251
|
+
#must be the first message the client sends
|
252
|
+
mp["client_info"]={
|
253
|
+
"client_id"=>:string,
|
254
|
+
"listen_port"=>:int
|
255
|
+
}
|
256
|
+
|
257
|
+
mp["ask_info"]={
|
258
|
+
"url"=>:url
|
259
|
+
}
|
260
|
+
|
261
|
+
mp["tell_info"]={
|
262
|
+
"url"=>:url,
|
263
|
+
"size"=>Optional.new(:int),
|
264
|
+
"chunk_size"=>Optional.new(:int),
|
265
|
+
"streaming"=>Optional.new(:bool)
|
266
|
+
}
|
267
|
+
|
268
|
+
mp["ask_verify"]={
|
269
|
+
"peer"=>:ip,
|
270
|
+
"url"=>:url,
|
271
|
+
"range"=>:range,
|
272
|
+
"peer_id"=>:string
|
273
|
+
}
|
274
|
+
|
275
|
+
mp["tell_verify"]={
|
276
|
+
"peer"=>:ip,
|
277
|
+
"url"=>:url,
|
278
|
+
"range"=>:range,
|
279
|
+
"peer_id"=>:string,
|
280
|
+
"is_authorized"=>:bool
|
281
|
+
}
|
282
|
+
|
283
|
+
mp["request"]={
|
284
|
+
"url"=>:url,
|
285
|
+
"range"=>Optional.new(:range)
|
286
|
+
}
|
287
|
+
|
288
|
+
mp["provide"]={
|
289
|
+
"url"=>:url,
|
290
|
+
"range"=>Optional.new(:range)
|
291
|
+
}
|
292
|
+
|
293
|
+
mp["unrequest"]={
|
294
|
+
"url"=>:url,
|
295
|
+
"range"=>Optional.new(:range)
|
296
|
+
}
|
297
|
+
|
298
|
+
mp["unprovide"]={
|
299
|
+
"url"=>:url,
|
300
|
+
"range"=>Optional.new(:range)
|
301
|
+
}
|
302
|
+
|
303
|
+
#the taker sends this message when a transfer finishes
|
304
|
+
#if there is an error in the transfer, dont set a hash
|
305
|
+
#to signify failure
|
306
|
+
#when this is received from the taker, the connection is considered done for all parties
|
307
|
+
#
|
308
|
+
#The giver also sends this message when they are done transferring.
|
309
|
+
#this closes the connection on their side, allowing them to start other transfers
|
310
|
+
#It leaves the connection open on the taker side to allow them to decide if the transfer was successful
|
311
|
+
#the hash parameter is ignored when sent by the giver
|
312
|
+
mp["completed"]={
|
313
|
+
#"peer"=>:ip, no longer used
|
314
|
+
"url"=>:url,
|
315
|
+
"range"=>:range,
|
316
|
+
"peer_id"=>:string,
|
317
|
+
"hash"=>Optional.new(:string)
|
318
|
+
}
|
319
|
+
|
320
|
+
mp["hash_verify"]={
|
321
|
+
"url"=>:url,
|
322
|
+
"range"=>:range,
|
323
|
+
"hash_ok"=>:bool
|
324
|
+
}
|
325
|
+
|
326
|
+
mp["transfer"]={
|
327
|
+
"host"=>:string,
|
328
|
+
"port"=>:int,
|
329
|
+
"method"=>:string,
|
330
|
+
"url"=>:url,
|
331
|
+
"range"=>:range,
|
332
|
+
"peer_id"=>:string
|
333
|
+
}
|
334
|
+
|
335
|
+
mp["protocol_error"]={
|
336
|
+
"message"=>Optional.new(:string)
|
337
|
+
}
|
338
|
+
|
339
|
+
mp["protocol_warn"]={
|
340
|
+
"message"=>Optional.new(:string)
|
341
|
+
}
|
342
|
+
|
343
|
+
mp
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|