rbus 0.1.0
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.
- data/CHANGELOG.txt +9 -0
- data/COPYING.txt +341 -0
- data/HACKING.txt +104 -0
- data/Manifest.txt +58 -0
- data/README.txt +17 -0
- data/Rakefile +97 -0
- data/TUTORIAL.txt +303 -0
- data/bin/rbus-send +165 -0
- data/examples/async_rb_and_notify.rb +56 -0
- data/examples/async_rb_loop.rb +52 -0
- data/examples/glib_async_rb_loop.rb +57 -0
- data/examples/glib_rhythmbox.rb +54 -0
- data/examples/hal_device_info.rb +54 -0
- data/examples/listnames.rb +37 -0
- data/examples/network_manager_get_properties.rb +50 -0
- data/examples/notification_bubble.rb +46 -0
- data/examples/rhythmbox_print_playing_uri.rb +41 -0
- data/examples/rhythmbox_signal_print_playing.rb +54 -0
- data/examples/rhythmbox_start_service.rb +39 -0
- data/examples/rhythmbox_toggle_playing.rb +46 -0
- data/lib/rbus.rb +25 -0
- data/lib/rbus/auth/auth.rb +53 -0
- data/lib/rbus/auth/dbus_cookie_sha1.rb +66 -0
- data/lib/rbus/auth/dummy.rb +37 -0
- data/lib/rbus/auth/external.rb +34 -0
- data/lib/rbus/auth/state_machine.rb +168 -0
- data/lib/rbus/bus/bus.rb +101 -0
- data/lib/rbus/bus/proxy.rb +137 -0
- data/lib/rbus/bus/transport.rb +125 -0
- data/lib/rbus/default.rb +29 -0
- data/lib/rbus/etc/exception.rb +44 -0
- data/lib/rbus/etc/log.rb +100 -0
- data/lib/rbus/etc/types.rb +56 -0
- data/lib/rbus/etc/version.rb +34 -0
- data/lib/rbus/glib.rb +29 -0
- data/lib/rbus/mainloop/glib.rb +77 -0
- data/lib/rbus/mainloop/mainloop.rb +84 -0
- data/lib/rbus/mainloop/observers.rb +149 -0
- data/lib/rbus/mainloop/thread.rb +78 -0
- data/lib/rbus/message/constants.rb +51 -0
- data/lib/rbus/message/marshal.rb +139 -0
- data/lib/rbus/message/message.rb +110 -0
- data/lib/rbus/message/reader.rb +108 -0
- data/lib/rbus/message/serial_generator.rb +48 -0
- data/lib/rbus/message/unmarshal.rb +171 -0
- data/lib/rbus/message/writer.rb +69 -0
- data/setup.rb +1608 -0
- data/spec/auth_spec.rb +123 -0
- data/spec/bus_spec.rb +178 -0
- data/spec/helpers/bus_mocks.rb +64 -0
- data/spec/helpers/spec_helper.rb +24 -0
- data/spec/mainloop_spec.rb +74 -0
- data/spec/marshal_spec.rb +91 -0
- data/spec/message_spec.rb +61 -0
- data/spec/observers_spec.rb +28 -0
- data/spec/proxy_spec.rb +120 -0
- data/spec/transport_spec.rb +187 -0
- data/spec/unmarshal_spec.rb +186 -0
- metadata +118 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
module RBus
|
25
|
+
module Auth
|
26
|
+
|
27
|
+
# Implements the AUTH EXTERNAL mechanism.
|
28
|
+
class External
|
29
|
+
def auth
|
30
|
+
['EXTERNAL', Process.uid, :OK]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
module RBus
|
25
|
+
module Auth
|
26
|
+
|
27
|
+
# Implements a statemachine for Authorization like the one described in
|
28
|
+
# the D-Bus specification.
|
29
|
+
class StateMachine
|
30
|
+
def initialize(transport)
|
31
|
+
@transport = transport
|
32
|
+
@mechanism = FIRST_MECHANISM.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Main entry point, returns guid if succesful, or raises otherwise.
|
36
|
+
def authorize
|
37
|
+
state = :AUTH
|
38
|
+
|
39
|
+
loop do
|
40
|
+
case state
|
41
|
+
when :AUTH
|
42
|
+
command, data, state = @mechanism.auth
|
43
|
+
send_auth(command, data)
|
44
|
+
|
45
|
+
when :REJECTED
|
46
|
+
response = Command.new(@transport.readline)
|
47
|
+
case response.command
|
48
|
+
when 'REJECTED'
|
49
|
+
next_mechanism(response.data)
|
50
|
+
state = :AUTH
|
51
|
+
else
|
52
|
+
raise AuthException
|
53
|
+
end
|
54
|
+
|
55
|
+
when :OK
|
56
|
+
response = Command.new(@transport.readline)
|
57
|
+
case response.command
|
58
|
+
when 'OK'
|
59
|
+
send_begin
|
60
|
+
return response.data
|
61
|
+
when 'REJECT'
|
62
|
+
next_mechanism(response.data)
|
63
|
+
state = :AUTH
|
64
|
+
when 'DATA'
|
65
|
+
send_cancel
|
66
|
+
state = :REJECT
|
67
|
+
when 'ERROR'
|
68
|
+
send_cancel
|
69
|
+
state = :REJECT
|
70
|
+
else
|
71
|
+
send_error
|
72
|
+
state = :OK
|
73
|
+
end
|
74
|
+
|
75
|
+
when :DATA
|
76
|
+
response = Command.new(@transport.readline)
|
77
|
+
case response.command
|
78
|
+
when 'DATA'
|
79
|
+
begin
|
80
|
+
message, state = @mechanism.data(response.data)
|
81
|
+
rescue AuthException => e
|
82
|
+
send_cancel
|
83
|
+
state = :REJECT
|
84
|
+
else
|
85
|
+
send_data(message)
|
86
|
+
end
|
87
|
+
when 'REJECT'
|
88
|
+
next_mechanism(response.data)
|
89
|
+
state = :AUTH
|
90
|
+
when 'ERROR'
|
91
|
+
send_cancel
|
92
|
+
state = :REJECT
|
93
|
+
when 'OK'
|
94
|
+
send_begin
|
95
|
+
return response.data
|
96
|
+
else
|
97
|
+
send_error
|
98
|
+
state = :DATA
|
99
|
+
end
|
100
|
+
else
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Move to next mechanism
|
107
|
+
def next_mechanism(data)
|
108
|
+
if @mechanisms.nil?
|
109
|
+
populate_mechanisms(data)
|
110
|
+
end
|
111
|
+
if @mechanisms.empty?
|
112
|
+
raise AuthException
|
113
|
+
else
|
114
|
+
@mechanism = @mechanisms.shift.new
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Build initial mechanism list
|
119
|
+
def populate_mechanisms(data)
|
120
|
+
server_mechanisms = data.split(' ')
|
121
|
+
|
122
|
+
# Find common mechanisms, or try them all if server didn't tell us
|
123
|
+
if server_mechanisms.empty?
|
124
|
+
mechanisms = MECHANISMS.keys
|
125
|
+
else
|
126
|
+
mechanisms = (server_mechanisms & MECHANISMS.keys)
|
127
|
+
end
|
128
|
+
|
129
|
+
@mechanisms = mechanisms.map{|name| MECHANISMS[name]}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Send AUTH ...
|
133
|
+
def send_auth(command, data)
|
134
|
+
line = ['AUTH', command, hex_encode(data)].compact.join(' ')
|
135
|
+
@transport.sendline(line)
|
136
|
+
end
|
137
|
+
|
138
|
+
# send_begin => BEGIN, etc
|
139
|
+
def method_missing(symbol, *args)
|
140
|
+
command = symbol.to_s
|
141
|
+
if command.sub!('send_', '')
|
142
|
+
line = [command.upcase, hex_encode(args[0])].compact.join(' ')
|
143
|
+
@transport.sendline(line)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Server expects all data to be encoded in hex
|
148
|
+
def hex_encode(plain)
|
149
|
+
return nil if plain.nil?
|
150
|
+
plain.to_s.unpack('H*')[0]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Parses server response and splits it into component parts
|
155
|
+
# Decodes where appropriate (only DATA)
|
156
|
+
class Command
|
157
|
+
attr_reader :command, :data
|
158
|
+
def initialize(string)
|
159
|
+
@command, data = string.split(/ /, 2)
|
160
|
+
@data = (@command == 'DATA') ? hex_decode(data) : data
|
161
|
+
end
|
162
|
+
|
163
|
+
def hex_decode(encoded)
|
164
|
+
encoded.scan(/[[:xdigit:]]{2}/).map{|h|h.hex.chr}.join
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/rbus/bus/bus.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
|
25
|
+
require File.dirname(__FILE__) + '/proxy'
|
26
|
+
require 'singleton'
|
27
|
+
|
28
|
+
module RBus
|
29
|
+
|
30
|
+
# Get the session bus
|
31
|
+
def self.session_bus
|
32
|
+
SessionBus.instance
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get the system bus
|
36
|
+
def self.system_bus
|
37
|
+
SystemBus.instance
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get Bus with specific address
|
41
|
+
def self.get_bus(address)
|
42
|
+
Bus.new(address)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Represents a connection to a Bus. It is also a Proxy object for
|
47
|
+
# sending messages to the bus itself.
|
48
|
+
class Bus < Proxy
|
49
|
+
NAMES = []
|
50
|
+
|
51
|
+
attr_reader :message_loop
|
52
|
+
|
53
|
+
# Connect to Bus with +server_address+, which can contain multiple
|
54
|
+
# addresses to try, separated by semicolons.
|
55
|
+
def initialize(server_address)
|
56
|
+
raise InvalidAddressException, 'Empty address' if server_address.empty?
|
57
|
+
|
58
|
+
# Can be several addresses, separated by ;
|
59
|
+
server_address.split(';').each do |address|
|
60
|
+
@transport = Transport.connect(address)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Not used for anything, save at all?
|
64
|
+
@guid = Auth.authorize(@transport)
|
65
|
+
|
66
|
+
# Start main message loop.
|
67
|
+
#@message_loop = Message::Loop.new(@transport).run
|
68
|
+
@message_loop = Mainloop.new(@transport)
|
69
|
+
|
70
|
+
@well_known_name = 'org.freedesktop.DBus'
|
71
|
+
@object_path = '/org/freedesktop/DBus'
|
72
|
+
@interface = 'org.freedesktop.DBus'
|
73
|
+
NAMES << Hello()
|
74
|
+
Introspect()
|
75
|
+
|
76
|
+
Log.debug('Bus names:', NAMES)
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a remote object for well-known name and object path.
|
81
|
+
def get_object(well_known_name, object_path)
|
82
|
+
Proxy.new(self, well_known_name, object_path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Represents the connection to the Session Bus.
|
87
|
+
class SessionBus < Bus
|
88
|
+
include Singleton
|
89
|
+
def initialize
|
90
|
+
super(ENV['DBUS_SESSION_BUS_ADDRESS'])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Represents the connection to the System Bus.
|
95
|
+
class SystemBus < Bus
|
96
|
+
include Singleton
|
97
|
+
def initialize
|
98
|
+
super(ENV['DBUS_SYSTEM_BUS_ADDRESS'] || 'unix:path=/var/run/dbus/system_bus_socket')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
require 'rexml/document'
|
25
|
+
|
26
|
+
module RBus
|
27
|
+
|
28
|
+
VALID_METHOD_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,254}$/
|
29
|
+
|
30
|
+
# BlankSlate is a superclass that empties (almost) all instance methods
|
31
|
+
# from it's subclasses.
|
32
|
+
# See: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
33
|
+
class BlankSlate
|
34
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|[!\?]$/ }
|
35
|
+
end
|
36
|
+
|
37
|
+
# The object that represents the remote bus object.
|
38
|
+
class Proxy < BlankSlate
|
39
|
+
|
40
|
+
# Parses Introspect() XML into a hash with
|
41
|
+
# {method => signature}
|
42
|
+
def self.parse_introspect(xml)
|
43
|
+
methods = {}
|
44
|
+
doc = REXML::Document.new(xml)
|
45
|
+
doc.elements.each("//method") do |method|
|
46
|
+
name = method.attribute('name').to_s
|
47
|
+
signature = ''
|
48
|
+
method.elements.each("*[@direction!='out']") do |arg|
|
49
|
+
signature << arg.attribute('type').to_s
|
50
|
+
end
|
51
|
+
methods[name] = signature
|
52
|
+
end
|
53
|
+
methods
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(bus, well_known_name, object_path)
|
57
|
+
@bus = bus
|
58
|
+
@message_loop = @bus.message_loop
|
59
|
+
@well_known_name = well_known_name
|
60
|
+
@object_path = object_path
|
61
|
+
Introspect()
|
62
|
+
end
|
63
|
+
|
64
|
+
def Introspect # :nodoc:
|
65
|
+
# TODO: store interfaces as well, if ever needed
|
66
|
+
interface = @interface
|
67
|
+
@interface = 'org.freedesktop.DBus.Introspectable'
|
68
|
+
@methods = Proxy.parse_introspect(method_missing(:Introspect))
|
69
|
+
Log.debug("Methods from introspect: " + @methods.inspect)
|
70
|
+
@interface = interface
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set interface and return or yield self.
|
74
|
+
# If a block is given, a copy of the object will be
|
75
|
+
# the new interface will only be set for that block, and reset
|
76
|
+
# afterwards.
|
77
|
+
def interface!(interface)
|
78
|
+
old_i = @interface
|
79
|
+
@interface = interface
|
80
|
+
if block_given?
|
81
|
+
yield self
|
82
|
+
@interface = old_i
|
83
|
+
end
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Connect block to a signal
|
88
|
+
def connect!(signal, options = {}, &block)
|
89
|
+
raise MessageException, "connect! needs a block" unless block_given?
|
90
|
+
|
91
|
+
rules = {
|
92
|
+
:type => 'signal',
|
93
|
+
:member => signal.to_s,
|
94
|
+
:path => @object_path,
|
95
|
+
#:sender => @well_known_name
|
96
|
+
}
|
97
|
+
rules['interface'] = @interface if @interface
|
98
|
+
|
99
|
+
rules.merge!(options)
|
100
|
+
|
101
|
+
Log.debug(rules)
|
102
|
+
@bus.AddMatch(rules, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Define a method with signature.
|
106
|
+
# +name+ can be a string or a symbol.
|
107
|
+
def method!(name, signature)
|
108
|
+
@methods[name.to_s] = signature
|
109
|
+
end
|
110
|
+
|
111
|
+
# Routes all method calls to remote object
|
112
|
+
def method_missing(name, *args, &block) # :nodoc:
|
113
|
+
|
114
|
+
method = name.to_s
|
115
|
+
|
116
|
+
Log.debug('Proxy method called: ' + method)
|
117
|
+
|
118
|
+
unless method.match(VALID_METHOD_NAME)
|
119
|
+
raise InvalidNameException, "Invalid method name '#{name}'"
|
120
|
+
end
|
121
|
+
|
122
|
+
call = MethodCall.new do |m|
|
123
|
+
m.destination = @well_known_name
|
124
|
+
m.object_path = @object_path
|
125
|
+
m.interface = @interface if @interface
|
126
|
+
m.member = method
|
127
|
+
m.arguments = args
|
128
|
+
if !args.empty? && @methods.has_key?(method)
|
129
|
+
Log.debug('Signature: ' + @methods[method])
|
130
|
+
m.signature = @methods[method]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
Log.debug(block_given? ? 'Block given' : 'Block NOT given')
|
134
|
+
@message_loop.send_message(call, block_given? ? block : nil)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
require 'socket'
|
25
|
+
|
26
|
+
module RBus
|
27
|
+
|
28
|
+
# Transport represents the connection to the bus,
|
29
|
+
# and supports sending and recieving messages
|
30
|
+
# The class itself is a factory that returns the
|
31
|
+
# appropriate transport for an address
|
32
|
+
class Transport
|
33
|
+
|
34
|
+
# Abstract base implementation of a transport.
|
35
|
+
class AbstractTransport
|
36
|
+
# Newline
|
37
|
+
CRLF = "\015\012"
|
38
|
+
attr_reader :socket
|
39
|
+
|
40
|
+
def initialize(connection_info)
|
41
|
+
@connection_info = connection_info
|
42
|
+
create_socket
|
43
|
+
end
|
44
|
+
|
45
|
+
def send(message)
|
46
|
+
@socket.write(message)
|
47
|
+
end
|
48
|
+
|
49
|
+
def read(len = 2**27)
|
50
|
+
@socket.read(len)
|
51
|
+
#@socket.recv(len)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sendline(message)
|
55
|
+
@socket.write(message + CRLF)
|
56
|
+
end
|
57
|
+
|
58
|
+
def readline
|
59
|
+
@socket.readline.chomp
|
60
|
+
end
|
61
|
+
|
62
|
+
def close
|
63
|
+
@socket.close
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Represents a connection to a UNIX Socket, abstract or regular.
|
69
|
+
class UNIXTransport < AbstractTransport
|
70
|
+
def create_socket
|
71
|
+
if @connection_info.has_key?('abstract')
|
72
|
+
@socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
73
|
+
@socket.connect("\1\0\0#{@connection_info['abstract']}")
|
74
|
+
elsif @connection_info.has_key?('path')
|
75
|
+
@socket = UNIXSocket.open(@connection_info['path'])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Represents a connection to a TCP Socket.
|
81
|
+
class TCPTransport < AbstractTransport
|
82
|
+
def create_socket
|
83
|
+
@socket = TCPSocket.new(@connection_info['host'], @connection_info['port'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Unescape simple hex encoding (%XX) for values
|
88
|
+
def self.unescape(value)
|
89
|
+
value.gsub(/%([[:xdigit:]]{2})/) {$1.hex.chr}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parse a valid connection address
|
93
|
+
def self.parse_address(address)
|
94
|
+
connection_info = {}
|
95
|
+
connection_info['transport'], info = address.split(':', 2)
|
96
|
+
|
97
|
+
raise InvalidAddressException if info.nil? || info.empty?
|
98
|
+
|
99
|
+
info.split(',').each do |pair|
|
100
|
+
key, value = pair.split('=')
|
101
|
+
connection_info[key] = unescape(value)
|
102
|
+
end
|
103
|
+
connection_info
|
104
|
+
end
|
105
|
+
|
106
|
+
# Define factory method connect
|
107
|
+
class << self
|
108
|
+
def connect(address)
|
109
|
+
connection_info = parse_address(address)
|
110
|
+
|
111
|
+
klass =
|
112
|
+
case connection_info['transport']
|
113
|
+
when 'unix'
|
114
|
+
UNIXTransport
|
115
|
+
when 'tcp'
|
116
|
+
TCPTransport
|
117
|
+
else
|
118
|
+
raise InvalidTransportException, "Unknown transport #{connection_info['transport']}"
|
119
|
+
end
|
120
|
+
klass::new(connection_info)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|