secure_carrot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,209 @@
1
+ module Carrot::AMQP
2
+ module Protocol
3
+ #:stopdoc:
4
+ class Class::Method
5
+ def initialize *args
6
+ opts = args.pop if args.last.is_a? Hash
7
+ opts ||= {}
8
+
9
+ @debug = 1 # XXX hack, p(obj) == '' if no instance vars are set
10
+
11
+ if args.size == 1 and args.first.is_a? Buffer
12
+ buf = args.shift
13
+ else
14
+ buf = nil
15
+ end
16
+
17
+ self.class.arguments.each do |type, name|
18
+ val = buf ? buf.read(type) :
19
+ args.shift || opts[name] || opts[name.to_s]
20
+ instance_variable_set("@#{name}", val)
21
+ end
22
+ end
23
+
24
+ def arguments
25
+ self.class.arguments.inject({}) do |hash, (type, name)|
26
+ hash.update name => instance_variable_get("@#{name}")
27
+ end
28
+ end
29
+
30
+ def to_binary
31
+ buf = Buffer.new
32
+ buf.write :short, self.class.parent.id
33
+ buf.write :short, self.class.id
34
+
35
+ bits = []
36
+
37
+ self.class.arguments.each do |type, name|
38
+ val = instance_variable_get("@#{name}")
39
+ if type == :bit
40
+ bits << (val || false)
41
+ else
42
+ unless bits.empty?
43
+ buf.write :bit, bits
44
+ bits = []
45
+ end
46
+ buf.write type, val
47
+ end
48
+ end
49
+
50
+ buf.write :bit, bits unless bits.empty?
51
+ buf.rewind
52
+
53
+ buf
54
+ end
55
+
56
+ def to_s
57
+ to_binary.to_s
58
+ end
59
+
60
+ def to_frame channel = 0
61
+ Frame::Method.new(self, channel)
62
+ end
63
+ end
64
+
65
+ #:startdoc:
66
+ #
67
+ # Contains a properties hash that holds some potentially interesting
68
+ # information.
69
+ # * :delivery_mode
70
+ # 1 equals transient.
71
+ # 2 equals persistent. Unconsumed persistent messages will survive
72
+ # a server restart when they are stored in a durable queue.
73
+ # * :redelivered
74
+ # True or False
75
+ # * :routing_key
76
+ # The routing string used for matching this message to this queue.
77
+ # * :priority
78
+ # An integer in the range of 0 to 9 inclusive.
79
+ # * :content_type
80
+ # Always "application/octet-stream" (byte stream)
81
+ # * :exchange
82
+ # The source exchange which published this message.
83
+ # * :message_count
84
+ # The number of unconsumed messages contained in the queue.
85
+ # * :delivery_tag
86
+ # A monotonically increasing integer. This number should not be trusted
87
+ # as a sequence number. There is no guarantee it won't get reset.
88
+ class Header
89
+ def initialize *args
90
+ opts = args.pop if args.last.is_a? Hash
91
+ opts ||= {}
92
+
93
+ first = args.shift
94
+
95
+ if first.is_a? ::Class and first.ancestors.include? Protocol::Class
96
+ @klass = first
97
+ @size = args.shift || 0
98
+ @weight = args.shift || 0
99
+ @properties = opts
100
+
101
+ elsif first.is_a? Buffer or first.is_a? String
102
+ buf = first
103
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
104
+
105
+ @klass = Protocol.classes[buf.read(:short)]
106
+ @weight = buf.read(:short)
107
+ @size = buf.read(:longlong)
108
+
109
+ props = buf.read(:properties, *klass.properties.map{|type,_| type })
110
+ @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
111
+
112
+ else
113
+ raise ArgumentError, 'Invalid argument'
114
+ end
115
+
116
+ end
117
+ attr_accessor :klass, :size, :weight, :properties
118
+
119
+ def to_binary
120
+ buf = Buffer.new
121
+ buf.write :short, klass.id
122
+ buf.write :short, weight # XXX rabbitmq only supports weight == 0
123
+ buf.write :longlong, size
124
+ buf.write :properties, (klass.properties.map do |type, name|
125
+ [ type, properties[name] || properties[name.to_s] ]
126
+ end)
127
+ buf.rewind
128
+ buf
129
+ end
130
+
131
+ def to_s
132
+ to_binary.to_s
133
+ end
134
+
135
+ def to_frame channel = 0
136
+ Frame::Header.new(self, channel)
137
+ end
138
+
139
+ def == header
140
+ [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
141
+ eql and __send__(field) == header.__send__(field)
142
+ end
143
+ end
144
+
145
+ def method_missing meth, *args, &blk
146
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] :
147
+ super
148
+ end
149
+ end
150
+
151
+ def self.parse buf
152
+ buf = Buffer.new(buf) unless buf.is_a? Buffer
153
+ class_id, method_id = buf.read(:short, :short)
154
+ classes[class_id].methods[method_id].new(buf)
155
+ end
156
+ #:stopdoc:
157
+ end
158
+ end
159
+
160
+ if $0 =~ /bacon/ or $0 == __FILE__
161
+ require 'bacon'
162
+ include AMQP
163
+
164
+ describe Protocol do
165
+ should 'instantiate methods with arguments' do
166
+ meth = Protocol::Connection::StartOk.new nil, 'PLAIN', nil, 'en_US'
167
+ meth.locale.should == 'en_US'
168
+ end
169
+
170
+ should 'instantiate methods with named parameters' do
171
+ meth = Protocol::Connection::StartOk.new :locale => 'en_US',
172
+ :mechanism => 'PLAIN'
173
+ meth.locale.should == 'en_US'
174
+ end
175
+
176
+ should 'convert methods to binary' do
177
+ meth = Protocol::Connection::Secure.new :challenge => 'secret'
178
+ meth.to_binary.should.be.kind_of? Buffer
179
+
180
+ meth.to_s.should == [ 10, 20, 6, 'secret' ].pack('nnNa*')
181
+ end
182
+
183
+ should 'convert binary to method' do
184
+ orig = Protocol::Connection::Secure.new :challenge => 'secret'
185
+ copy = Protocol.parse orig.to_binary
186
+ orig.should == copy
187
+ end
188
+
189
+ should 'convert headers to binary' do
190
+ head = Protocol::Header.new Protocol::Basic,
191
+ size = 5,
192
+ weight = 0,
193
+ :content_type => 'text/json',
194
+ :delivery_mode => 1,
195
+ :priority => 1
196
+ head.to_s.should == [ 60, weight, 0, size, 0b1001_1000_0000_0000, 9, 'text/json', 1, 1 ].pack('nnNNnCa*CC')
197
+ end
198
+
199
+ should 'convert binary to header' do
200
+ orig = Protocol::Header.new Protocol::Basic,
201
+ size = 5,
202
+ weight = 0,
203
+ :content_type => 'text/json',
204
+ :delivery_mode => 1,
205
+ :priority => 1
206
+ Protocol::Header.new(orig.to_binary).should == orig
207
+ end
208
+ end
209
+ end
data/lib/amqp/queue.rb ADDED
@@ -0,0 +1,144 @@
1
+ module Carrot::AMQP
2
+ class Queue
3
+ attr_reader :name, :carrot
4
+ attr_accessor :delivery_tag
5
+
6
+ def initialize(carrot, name, opts = {})
7
+ @opts = opts
8
+ @name = name
9
+ @carrot = carrot
10
+ server.send_frame(
11
+ Protocol::Queue::Declare.new({ :queue => name, :nowait => true }.merge(opts))
12
+ )
13
+ end
14
+
15
+ def pop(opts = {})
16
+ self.delivery_tag = nil
17
+ server.send_frame(
18
+ Protocol::Basic::Get.new({ :queue => name, :consumer_tag => name, :no_ack => !opts.delete(:ack), :nowait => true }.merge(opts))
19
+ )
20
+ method = server.next_method
21
+ return unless method.kind_of?(Protocol::Basic::GetOk)
22
+
23
+ self.delivery_tag = method.delivery_tag
24
+
25
+ header = server.next_payload
26
+
27
+ msg = ''
28
+ while msg.length < header.size
29
+ msg << server.next_payload
30
+ end
31
+
32
+ msg
33
+ end
34
+
35
+ def ack
36
+ server.send_frame(
37
+ Protocol::Basic::Ack.new(:delivery_tag => delivery_tag)
38
+ )
39
+ end
40
+
41
+ def publish(data, opts = {})
42
+ exchange.publish(data, opts)
43
+ end
44
+
45
+ def message_count
46
+ status.first
47
+ end
48
+
49
+ def consumer_count
50
+ status.last
51
+ end
52
+
53
+ def status(opts = {}, &blk)
54
+ server.send_frame(
55
+ Protocol::Queue::Declare.new({ :queue => name, :passive => true }.merge(opts))
56
+ )
57
+ method = server.next_method
58
+ return [nil, nil] if method.kind_of?(Protocol::Connection::Close)
59
+
60
+ [method.message_count, method.consumer_count]
61
+ end
62
+
63
+ def bind(exchange, opts = {})
64
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
65
+ bindings[exchange] = opts
66
+ server.send_frame(
67
+ Protocol::Queue::Bind.new({ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts))
68
+ )
69
+ end
70
+
71
+ def unbind(exchange, opts = {})
72
+ exchange = exchange.respond_to?(:name) ? exchange.name : exchange
73
+ bindings.delete(exchange)
74
+
75
+ server.send_frame(
76
+ Protocol::Queue::Unbind.new({
77
+ :queue => name, :exchange => exchange, :routing_key => opts.delete(:key), :nowait => true }.merge(opts)
78
+ )
79
+ )
80
+ end
81
+
82
+ def delete(opts = {})
83
+ server.send_frame(
84
+ Protocol::Queue::Delete.new({ :queue => name, :nowait => true }.merge(opts))
85
+ )
86
+ carrot.queues.delete(name)
87
+ end
88
+
89
+ def purge(opts = {})
90
+ server.send_frame(
91
+ Protocol::Queue::Purge.new({ :queue => name, :nowait => true }.merge(opts))
92
+ )
93
+ end
94
+
95
+ def server
96
+ carrot.server
97
+ end
98
+
99
+ ##
100
+ # Is a wrapper around publish to send persistent messages.
101
+
102
+ def send_message(data,opts={})
103
+ opts.merge!(:persistent => true)
104
+ exchange.publish(data,opts)
105
+ end
106
+
107
+ def encrypt_message(message, password)
108
+ encrypted_message = message.encrypt(:symmetric, :password => password)
109
+ encrypted_message
110
+ end
111
+
112
+ def decrypt_message(message, password)
113
+ decrypted_message = message.decrypt(:symmetric, :password => password)
114
+ decrypted_message
115
+ end
116
+
117
+ ##
118
+ # Is a wrapper around publish to send persistent and encrypted messages using symmetric key.
119
+
120
+ def encrypt_and_send_message(message, password, opts={})
121
+ opts.merge!(:persistent => true)
122
+ encrypted_message = encrypt_message(message, password)
123
+ exchange.publish(encrypted_message,opts)
124
+ end
125
+
126
+ ##
127
+ # This method will receive and decrypt messages using symmetric key.
128
+
129
+ def receive_and_decrypt_message(password, opts={})
130
+ msg = pop(opts)
131
+ decrypted_message = decrypt_message(msg, password)
132
+ decrypted_message
133
+ end
134
+
135
+ private
136
+ def exchange
137
+ @exchange ||= Exchange.new(carrot, :direct, '', :key => name)
138
+ end
139
+
140
+ def bindings
141
+ @bindings ||= {}
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,187 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'timeout'
4
+
5
+ module Carrot::AMQP
6
+ class Server
7
+ CONNECT_TIMEOUT = 1.0
8
+ RETRY_DELAY = 10.0
9
+ DEFAULT_PORT = 5672
10
+
11
+ attr_reader :host, :port, :status
12
+ attr_accessor :channel, :ticket
13
+
14
+ class ServerDown < StandardError; end
15
+ class ProtocolError < StandardError; end
16
+
17
+ def initialize(opts = {})
18
+ @host = opts[:host] || 'localhost'
19
+ @port = opts[:port] || DEFAULT_PORT
20
+ @user = opts[:user] || 'guest'
21
+ @pass = opts[:pass] || 'guest'
22
+ @vhost = opts[:vhost] || '/'
23
+ @insist = opts[:insist]
24
+ @status = 'NOT CONNECTED'
25
+
26
+ @multithread = opts[:multithread]
27
+ start_session
28
+ end
29
+
30
+ def send_frame(*args)
31
+ args.each do |data|
32
+ data.ticket = ticket if ticket and data.respond_to?(:ticket=)
33
+ data = data.to_frame(channel) unless data.is_a?(Frame)
34
+ data.channel = channel
35
+
36
+ log :send, data
37
+ write(data.to_s)
38
+ end
39
+ nil
40
+ end
41
+
42
+ def next_frame
43
+ frame = Frame.parse(buffer)
44
+ log :received, frame
45
+ frame
46
+ end
47
+
48
+ def next_method
49
+ next_payload
50
+ end
51
+
52
+ def next_payload
53
+ frame = next_frame
54
+ frame and frame.payload
55
+ end
56
+
57
+ def close
58
+ send_frame(
59
+ Protocol::Channel::Close.new(:reply_code => 200, :reply_text => 'bye', :method_id => 0, :class_id => 0)
60
+ )
61
+ puts "Error closing channel #{channel}" unless next_method.is_a?(Protocol::Channel::CloseOk)
62
+
63
+ self.channel = 0
64
+ send_frame(
65
+ Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0)
66
+ )
67
+ puts "Error closing connection" unless next_method.is_a?(Protocol::Connection::CloseOk)
68
+
69
+ rescue ServerDown => e
70
+ ensure
71
+ close_socket
72
+ end
73
+
74
+ def read(*args)
75
+ send_command(:read, *args)
76
+ end
77
+
78
+ def write(*args)
79
+ send_command(:write, *args)
80
+ end
81
+
82
+ private
83
+
84
+ def buffer
85
+ @buffer ||= Buffer.new(self)
86
+ end
87
+
88
+ def send_command(cmd, *args)
89
+ begin
90
+ socket.__send__(cmd, *args)
91
+ rescue Errno::EPIPE, IOError, Errno::ECONNRESET, Errno::EINVAL => e
92
+ raise ServerDown, e.message
93
+ end
94
+ end
95
+
96
+ def socket
97
+ return @socket if @socket and not @socket.closed?
98
+
99
+ begin
100
+ # Attempt to connect.
101
+ mutex.lock if multithread?
102
+ @socket = timeout(CONNECT_TIMEOUT) do
103
+ TCPSocket.new(host, port)
104
+ end
105
+
106
+ if Socket.constants.include? 'TCP_NODELAY'
107
+ @socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
108
+ end
109
+ @status = 'CONNECTED'
110
+ rescue SocketError, SystemCallError, IOError, Timeout::Error => e
111
+ msg = e.message << " - #{@host}:#{@port}"
112
+ raise ServerDown, e.message
113
+ ensure
114
+ mutex.unlock if multithread?
115
+ end
116
+
117
+ @socket
118
+ end
119
+
120
+ def start_session
121
+ @channel = 0
122
+ write(HEADER)
123
+ write([1, 1, VERSION_MAJOR, VERSION_MINOR].pack('C4'))
124
+ raise ProtocolError, 'bad start connection' unless next_method.is_a?(Protocol::Connection::Start)
125
+
126
+ send_frame(
127
+ Protocol::Connection::StartOk.new(
128
+ {:platform => 'Ruby', :product => 'Carrot', :information => 'http://github.com/famosagle/carrot', :version => VERSION},
129
+ 'AMQPLAIN',
130
+ {:LOGIN => @user, :PASSWORD => @pass},
131
+ 'en_US'
132
+ )
133
+ )
134
+
135
+ method = next_method
136
+ raise ProtocolError, "Bad AMQP Credentials. user: #{@user}, pass: #{@pass}" if method.nil?
137
+
138
+ if method.is_a?(Protocol::Connection::Tune)
139
+ send_frame(
140
+ Protocol::Connection::TuneOk.new( :channel_max => 0, :frame_max => 131072, :heartbeat => 0)
141
+ )
142
+ end
143
+
144
+ send_frame(
145
+ Protocol::Connection::Open.new(:virtual_host => @vhost, :capabilities => '', :insist => @insist)
146
+ )
147
+ raise ProtocolError, 'bad open connection' unless next_method.is_a?(Protocol::Connection::OpenOk)
148
+
149
+ @channel = 1
150
+ send_frame(Protocol::Channel::Open.new)
151
+ raise ProtocolError, "cannot open channel #{channel}" unless next_method.is_a?(Protocol::Channel::OpenOk)
152
+
153
+ send_frame(
154
+ Protocol::Access::Request.new(:realm => '/data', :read => true, :write => true, :active => true, :passive => true)
155
+ )
156
+ method = next_method
157
+ raise ProtocolError, 'access denied' unless method.is_a?(Protocol::Access::RequestOk)
158
+ self.ticket = method.ticket
159
+ end
160
+
161
+ def multithread?
162
+ @multithread
163
+ end
164
+
165
+ def close_socket(reason=nil)
166
+ # Close the socket. The server is not considered dead.
167
+ mutex.lock if multithread?
168
+ @socket.close if @socket and not @socket.closed?
169
+ @socket = nil
170
+ @status = "NOT CONNECTED"
171
+ ensure
172
+ mutex.unlock if multithread?
173
+ end
174
+
175
+ def mutex
176
+ @mutex ||= Mutex.new
177
+ end
178
+
179
+ def log(*args)
180
+ return unless Carrot.logging?
181
+ require 'pp'
182
+ pp args
183
+ puts
184
+ end
185
+
186
+ end
187
+ end