ruby-dbus 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/COPYING +504 -0
  2. data/NEWS +137 -0
  3. data/README +53 -0
  4. data/Rakefile +54 -0
  5. data/VERSION +1 -0
  6. data/doc/tutorial/index.html +356 -0
  7. data/doc/tutorial/index.markdown +467 -0
  8. data/examples/gdbus/gdbus +255 -0
  9. data/examples/gdbus/gdbus.glade +184 -0
  10. data/examples/gdbus/launch.sh +4 -0
  11. data/examples/no-introspect/nm-test.rb +21 -0
  12. data/examples/no-introspect/tracker-test.rb +16 -0
  13. data/examples/rhythmbox/playpause.rb +25 -0
  14. data/examples/service/call_service.rb +25 -0
  15. data/examples/service/service_newapi.rb +51 -0
  16. data/examples/simple/call_introspect.rb +34 -0
  17. data/examples/utils/listnames.rb +11 -0
  18. data/examples/utils/notify.rb +19 -0
  19. data/lib/dbus.rb +91 -0
  20. data/lib/dbus/auth.rb +258 -0
  21. data/lib/dbus/bus.rb +816 -0
  22. data/lib/dbus/core_ext/class/attribute.rb +91 -0
  23. data/lib/dbus/core_ext/kernel/singleton_class.rb +14 -0
  24. data/lib/dbus/core_ext/module/remove_method.rb +12 -0
  25. data/lib/dbus/error.rb +44 -0
  26. data/lib/dbus/export.rb +132 -0
  27. data/lib/dbus/introspect.rb +553 -0
  28. data/lib/dbus/marshall.rb +443 -0
  29. data/lib/dbus/matchrule.rb +100 -0
  30. data/lib/dbus/message.rb +310 -0
  31. data/lib/dbus/type.rb +222 -0
  32. data/ruby-dbus.gemspec +18 -0
  33. data/test/binding_test.rb +56 -0
  34. data/test/bus_driver_test.rb +22 -0
  35. data/test/dbus-launch-simple +35 -0
  36. data/test/dbus-limited-session.conf +28 -0
  37. data/test/server_robustness_test.rb +41 -0
  38. data/test/server_test.rb +53 -0
  39. data/test/service_newapi.rb +129 -0
  40. data/test/session_bus_test_manual.rb +20 -0
  41. data/test/signal_test.rb +64 -0
  42. data/test/t1 +4 -0
  43. data/test/t2.rb +66 -0
  44. data/test/t3-ticket27.rb +18 -0
  45. data/test/t5-report-dbus-interface.rb +58 -0
  46. data/test/t6-loop.rb +82 -0
  47. data/test/test_env +13 -0
  48. data/test/test_server +39 -0
  49. data/test/variant_test.rb +66 -0
  50. metadata +117 -0
@@ -0,0 +1,443 @@
1
+ # dbus.rb - Module containing the low-level D-Bus implementation
2
+ #
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License, version 2.1 as published by the Free Software Foundation.
9
+ # See the file "COPYING" for the exact licensing terms.
10
+
11
+ require 'socket'
12
+
13
+ # = D-Bus main module
14
+ #
15
+ # Module containing all the D-Bus modules and classes.
16
+ module DBus
17
+ # Exception raised when an invalid packet is encountered.
18
+ class InvalidPacketException < Exception
19
+ end
20
+
21
+ # = D-Bus packet unmarshaller class
22
+ #
23
+ # Class that handles the conversion (unmarshalling) of payload data
24
+ # to Array.
25
+ class PacketUnmarshaller
26
+ # Index pointer that points to the byte in the data that is
27
+ # currently being processed.
28
+ #
29
+ # Used to kown what part of the buffer has been consumed by unmarshalling.
30
+ # FIXME: Maybe should be accessed with a "consumed_size" method.
31
+ attr_reader :idx
32
+
33
+ # Create a new unmarshaller for the given data _buffer_ and _endianness_.
34
+ def initialize(buffer, endianness)
35
+ @buffy, @endianness = buffer.dup, endianness
36
+ if @endianness == BIG_END
37
+ @uint32 = "N"
38
+ @uint16 = "n"
39
+ @double = "G"
40
+ elsif @endianness == LIL_END
41
+ @uint32 = "V"
42
+ @uint16 = "v"
43
+ @double = "E"
44
+ else
45
+ # FIXME: shouldn't a more special exception be raised here?
46
+ # yes, idea for a good name ? :)
47
+ raise Exception, "Incorrect endianness"
48
+ end
49
+ @idx = 0
50
+ end
51
+
52
+ # Unmarshall the buffer for a given _signature_ and length _len_.
53
+ # Return an array of unmarshalled objects
54
+ def unmarshall(signature, len = nil)
55
+ if len != nil
56
+ if @buffy.size < @idx + len
57
+ raise IncompleteBufferException
58
+ end
59
+ end
60
+ sigtree = Type::Parser.new(signature).parse
61
+ ret = Array.new
62
+ sigtree.each do |elem|
63
+ ret << do_parse(elem)
64
+ end
65
+ ret
66
+ end
67
+
68
+ # Align the pointer index on a byte index of _a_, where a
69
+ # must be 1, 2, 4 or 8.
70
+ def align(a)
71
+ case a
72
+ when 1
73
+ when 2, 4, 8
74
+ bits = a - 1
75
+ @idx = @idx + bits & ~bits
76
+ raise IncompleteBufferException if @idx > @buffy.size
77
+ else
78
+ raise "Unsupported alignment #{a}"
79
+ end
80
+ end
81
+
82
+ ###############################################################
83
+ # FIXME: does anyone except the object itself call the above methods?
84
+ # Yes : Message marshalling code needs to align "body" to 8 byte boundary
85
+ private
86
+
87
+ # Retrieve the next _nbytes_ number of bytes from the buffer.
88
+ def get(nbytes)
89
+ raise IncompleteBufferException if @idx + nbytes > @buffy.size
90
+ ret = @buffy.slice(@idx, nbytes)
91
+ @idx += nbytes
92
+ ret
93
+ end
94
+
95
+ # Retrieve the series of bytes until the next NULL (\0) byte.
96
+ def get_nul_terminated
97
+ raise IncompleteBufferException if not @buffy[@idx..-1] =~ /^([^\0]*)\0/
98
+ str = $1
99
+ raise IncompleteBufferException if @idx + str.size + 1 > @buffy.size
100
+ @idx += str.size + 1
101
+ str
102
+ end
103
+
104
+ # Get the string length and string itself from the buffer.
105
+ # Return the string.
106
+ def get_string
107
+ align(4)
108
+ str_sz = get(4).unpack(@uint32)[0]
109
+ ret = @buffy.slice(@idx, str_sz)
110
+ raise IncompleteBufferException if @idx + str_sz + 1 > @buffy.size
111
+ @idx += str_sz
112
+ if @buffy[@idx].ord != 0
113
+ raise InvalidPacketException, "String is not nul-terminated"
114
+ end
115
+ @idx += 1
116
+ # no exception, see check above
117
+ ret
118
+ end
119
+
120
+ # Get the signature length and signature itself from the buffer.
121
+ # Return the signature.
122
+ def get_signature
123
+ str_sz = get(1).unpack('C')[0]
124
+ ret = @buffy.slice(@idx, str_sz)
125
+ raise IncompleteBufferException if @idx + str_sz + 1 >= @buffy.size
126
+ @idx += str_sz
127
+ if @buffy[@idx].ord != 0
128
+ raise InvalidPacketException, "Type is not nul-terminated"
129
+ end
130
+ @idx += 1
131
+ # no exception, see check above
132
+ ret
133
+ end
134
+
135
+ # Based on the _signature_ type, retrieve a packet from the buffer
136
+ # and return it.
137
+ def do_parse(signature)
138
+ packet = nil
139
+ case signature.sigtype
140
+ when Type::BYTE
141
+ packet = get(1).unpack("C")[0]
142
+ when Type::UINT16
143
+ align(2)
144
+ packet = get(2).unpack(@uint16)[0]
145
+ when Type::INT16
146
+ align(4)
147
+ packet = get(4).unpack(@uint16)[0]
148
+ if (packet & 0x8000) != 0
149
+ packet -= 0x10000
150
+ end
151
+ when Type::UINT32
152
+ align(4)
153
+ packet = get(4).unpack(@uint32)[0]
154
+ when Type::INT32
155
+ align(4)
156
+ packet = get(4).unpack(@uint32)[0]
157
+ if (packet & 0x80000000) != 0
158
+ packet -= 0x100000000
159
+ end
160
+ when Type::UINT64
161
+ align(8)
162
+ packet_l = get(4).unpack(@uint32)[0]
163
+ packet_h = get(4).unpack(@uint32)[0]
164
+ if @endianness == LIL_END
165
+ packet = packet_l + packet_h * 2**32
166
+ else
167
+ packet = packet_l * 2**32 + packet_h
168
+ end
169
+ when Type::INT64
170
+ align(8)
171
+ packet_l = get(4).unpack(@uint32)[0]
172
+ packet_h = get(4).unpack(@uint32)[0]
173
+ if @endianness == LIL_END
174
+ packet = packet_l + packet_h * 2**32
175
+ else
176
+ packet = packet_l * 2**32 + packet_h
177
+ end
178
+ if (packet & 0x8000000000000000) != 0
179
+ packet -= 0x10000000000000000
180
+ end
181
+ when Type::DOUBLE
182
+ align(8)
183
+ packet = get(8).unpack(@double)[0]
184
+ when Type::BOOLEAN
185
+ align(4)
186
+ v = get(4).unpack(@uint32)[0]
187
+ raise InvalidPacketException if not [0, 1].member?(v)
188
+ packet = (v == 1)
189
+ when Type::ARRAY
190
+ align(4)
191
+ # checks please
192
+ array_sz = get(4).unpack(@uint32)[0]
193
+ raise InvalidPacketException if array_sz > 67108864
194
+
195
+ align(signature.child.alignment)
196
+ raise IncompleteBufferException if @idx + array_sz > @buffy.size
197
+
198
+ packet = Array.new
199
+ start_idx = @idx
200
+ while @idx - start_idx < array_sz
201
+ packet << do_parse(signature.child)
202
+ end
203
+
204
+ if signature.child.sigtype == Type::DICT_ENTRY then
205
+ packet = packet.inject(Hash.new) do |hash, pair|
206
+ hash[pair[0]] = pair[1]
207
+ hash
208
+ end
209
+ end
210
+ when Type::STRUCT
211
+ align(8)
212
+ packet = Array.new
213
+ signature.members.each do |elem|
214
+ packet << do_parse(elem)
215
+ end
216
+ when Type::VARIANT
217
+ string = get_signature
218
+ # error checking please
219
+ sig = Type::Parser.new(string).parse[0]
220
+ align(sig.alignment)
221
+ packet = do_parse(sig)
222
+ when Type::OBJECT_PATH
223
+ packet = get_string
224
+ when Type::STRING
225
+ packet = get_string
226
+ when Type::SIGNATURE
227
+ packet = get_signature
228
+ when Type::DICT_ENTRY
229
+ align(8)
230
+ key = do_parse(signature.members[0])
231
+ value = do_parse(signature.members[1])
232
+ packet = [key, value]
233
+ else
234
+ raise NotImplementedError,
235
+ "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})"
236
+ end
237
+ packet
238
+ end # def do_parse
239
+ end # class PacketUnmarshaller
240
+
241
+ # D-Bus packet marshaller class
242
+ #
243
+ # Class that handles the conversion (unmarshalling) of Ruby objects to
244
+ # (binary) payload data.
245
+ class PacketMarshaller
246
+ # The current or result packet.
247
+ # FIXME: allow access only when marshalling is finished
248
+ attr_reader :packet
249
+
250
+ # Create a new marshaller, setting the current packet to the
251
+ # empty packet.
252
+ def initialize(offset = 0)
253
+ @packet = ""
254
+ @offset = offset # for correct alignment of nested marshallers
255
+ end
256
+
257
+ # Round _n_ up to the specified power of two, _a_
258
+ def num_align(n, a)
259
+ case a
260
+ when 1, 2, 4, 8
261
+ bits = a - 1
262
+ n + bits & ~bits
263
+ else
264
+ raise "Unsupported alignment"
265
+ end
266
+ end
267
+
268
+ # Align the buffer with NULL (\0) bytes on a byte length of _a_.
269
+ def align(a)
270
+ @packet = @packet.ljust(num_align(@offset + @packet.length, a) - @offset, 0.chr)
271
+ end
272
+
273
+ # Append the the string _str_ itself to the packet.
274
+ def append_string(str)
275
+ align(4)
276
+ @packet += [str.length].pack("L") + str + "\0"
277
+ end
278
+
279
+ # Append the the signature _signature_ itself to the packet.
280
+ def append_signature(str)
281
+ @packet += str.length.chr + str + "\0"
282
+ end
283
+
284
+ # Append the array type _type_ to the packet and allow for appending
285
+ # the child elements.
286
+ def array(type)
287
+ # Thanks to Peter Rullmann for this line
288
+ align(4)
289
+ sizeidx = @packet.size
290
+ @packet += "ABCD"
291
+ align(type.alignment)
292
+ contentidx = @packet.size
293
+ yield
294
+ sz = @packet.size - contentidx
295
+ raise InvalidPacketException if sz > 67108864
296
+ @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
297
+ end
298
+
299
+ # Align and allow for appending struct fields.
300
+ def struct
301
+ align(8)
302
+ yield
303
+ end
304
+
305
+ # Append a string of bytes without type.
306
+ def append_simple_string(s)
307
+ @packet += s + "\0"
308
+ end
309
+
310
+ # Append a value _val_ to the packet based on its _type_.
311
+ def append(type, val)
312
+ raise TypeException, "Cannot send nil" if val.nil?
313
+
314
+ type = type.chr if type.kind_of?(Fixnum)
315
+ type = Type::Parser.new(type).parse[0] if type.kind_of?(String)
316
+ case type.sigtype
317
+ when Type::BYTE
318
+ @packet += val.chr
319
+ when Type::UINT32
320
+ align(4)
321
+ @packet += [val].pack("L")
322
+ when Type::UINT64
323
+ align(8)
324
+ @packet += [val].pack("Q")
325
+ when Type::INT64
326
+ align(8)
327
+ @packet += [val].pack("q")
328
+ when Type::INT32
329
+ align(4)
330
+ @packet += [val].pack("l")
331
+ when Type::UINT16
332
+ align(2)
333
+ @packet += [val].pack("S")
334
+ when Type::INT16
335
+ align(2)
336
+ @packet += [val].pack("s")
337
+ when Type::DOUBLE
338
+ align(8)
339
+ @packet += [val].pack("d")
340
+ when Type::BOOLEAN
341
+ align(4)
342
+ if val
343
+ @packet += [1].pack("L")
344
+ else
345
+ @packet += [0].pack("L")
346
+ end
347
+ when Type::OBJECT_PATH
348
+ append_string(val)
349
+ when Type::STRING
350
+ append_string(val)
351
+ when Type::SIGNATURE
352
+ append_signature(val)
353
+ when Type::VARIANT
354
+ vartype = nil
355
+ if val.is_a?(Array) and val.size == 2
356
+ if val[0].is_a?(DBus::Type::Type)
357
+ vartype, vardata = val
358
+ elsif val[0].is_a?(String)
359
+ begin
360
+ parsed = Type::Parser.new(val[0]).parse
361
+ vartype = parsed[0] if parsed.size == 1
362
+ vardata = val[1]
363
+ rescue Type::SignatureException
364
+ # no assignment
365
+ end
366
+ end
367
+ end
368
+ if vartype.nil?
369
+ vartype, vardata = PacketMarshaller.make_variant(val)
370
+ vartype = Type::Parser.new(vartype).parse[0]
371
+ end
372
+
373
+ append_signature(vartype.to_s)
374
+ align(vartype.alignment)
375
+ sub = PacketMarshaller.new(@offset + @packet.length)
376
+ sub.append(vartype, vardata)
377
+ @packet += sub.packet
378
+ when Type::ARRAY
379
+ if val.kind_of?(Hash)
380
+ raise TypeException, "Expected an Array but got a Hash" if type.child.sigtype != Type::DICT_ENTRY
381
+ # Damn ruby rocks here
382
+ val = val.to_a
383
+ end
384
+ if not val.kind_of?(Array)
385
+ raise TypeException, "Expected an Array but got a #{val.class}"
386
+ end
387
+ array(type.child) do
388
+ val.each do |elem|
389
+ append(type.child, elem)
390
+ end
391
+ end
392
+ when Type::STRUCT, Type::DICT_ENTRY
393
+ # TODO use duck typing, val.respond_to?
394
+ raise TypeException, "Struct/DE expects an Array" if not val.kind_of?(Array)
395
+ if type.sigtype == Type::DICT_ENTRY and val.size != 2
396
+ raise TypeException, "Dict entry expects a pair"
397
+ end
398
+ if type.members.size != val.size
399
+ raise TypeException, "Struct/DE has #{val.size} elements but type info for #{type.members.size}"
400
+ end
401
+ struct do
402
+ type.members.zip(val).each do |t, v|
403
+ append(t, v)
404
+ end
405
+ end
406
+ else
407
+ raise NotImplementedError,
408
+ "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
409
+ end
410
+ end # def append
411
+
412
+ # Make a [signature, value] pair for a variant
413
+ def self.make_variant(value)
414
+ # TODO: mix in _make_variant to String, Integer...
415
+ if value == true
416
+ ["b", true]
417
+ elsif value == false
418
+ ["b", false]
419
+ elsif value.nil?
420
+ ["b", nil]
421
+ elsif value.is_a? Float
422
+ ["d", value]
423
+ elsif value.is_a? Symbol
424
+ ["s", value.to_s]
425
+ elsif value.is_a? Array
426
+ ["av", value.map {|i| make_variant(i) }]
427
+ elsif value.is_a? Hash
428
+ h = {}
429
+ value.each_key {|k| h[k] = make_variant(value[k]) }
430
+ ["a{sv}", h]
431
+ elsif value.respond_to? :to_str
432
+ ["s", value.to_str]
433
+ elsif value.respond_to? :to_int
434
+ i = value.to_int
435
+ if -2_147_483_648 <= i && i < 2_147_483_648
436
+ ["i", i]
437
+ else
438
+ ["x", i]
439
+ end
440
+ end
441
+ end
442
+ end # class PacketMarshaller
443
+ end # module DBus
@@ -0,0 +1,100 @@
1
+ # This file is part of the ruby-dbus project
2
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
+ #
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License, version 2.1 as published by the Free Software Foundation.
7
+ # See the file "COPYING" for the exact licensing terms.
8
+
9
+ module DBus
10
+ # Exception raised when an erroneous match rule type is encountered.
11
+ class MatchRuleException < Exception
12
+ end
13
+
14
+ # = D-Bus match rule class
15
+ #
16
+ # FIXME
17
+ class MatchRule
18
+ # The list of possible match filters.
19
+ FILTERS = [:sender, :interface, :member, :path, :destination, :type]
20
+ # The sender filter.
21
+ attr_accessor :sender
22
+ # The interface filter.
23
+ attr_accessor :interface
24
+ # The member filter.
25
+ attr_accessor :member
26
+ # The path filter.
27
+ attr_accessor :path
28
+ # The destination filter.
29
+ attr_accessor :destination
30
+ # The type type that is matched.
31
+ attr_reader :type
32
+
33
+ # Create a new match rule
34
+ def initialize
35
+ @sender = @interface = @member = @path = @destination = @type = nil
36
+ end
37
+
38
+ # Set the message types to filter to type _t_.
39
+ # Possible message types are: signal, method_call, method_return, and error.
40
+ def type=(t)
41
+ if not ['signal', 'method_call', 'method_return', 'error'].member?(t)
42
+ raise MatchRuleException, t
43
+ end
44
+ @type = t
45
+ end
46
+
47
+ # Returns a match rule string version of the object.
48
+ # E.g.: "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo',path='/bar/foo',destination=':452345.34',arg2='bar'"
49
+ def to_s
50
+ FILTERS.select do |sym|
51
+ not method(sym).call.nil?
52
+ end.collect do |sym|
53
+ "#{sym.to_s}='#{method(sym).call}'"
54
+ end.join(",")
55
+ end
56
+
57
+ # Parses a match rule string _s_ and sets the filters on the object.
58
+ def from_s(str)
59
+ str.split(",").each do |eq|
60
+ if eq =~ /^(.*)='([^']*)'$/
61
+ # "
62
+ name = $1
63
+ val = $2
64
+ if FILTERS.member?(name.to_sym)
65
+ method(name + "=").call(val)
66
+ else
67
+ raise MatchRuleException, name
68
+ end
69
+ end
70
+ end
71
+ self
72
+ end
73
+
74
+ # Sets the match rule to filter for the given _signal_ and the
75
+ # given interface _intf_.
76
+ def from_signal(intf, signal)
77
+ signal = signal.name unless signal.is_a?(String)
78
+ self.type = "signal"
79
+ self.interface = intf.name
80
+ self.member = signal
81
+ self.path = intf.object.path
82
+ self
83
+ end
84
+
85
+ # Determines whether a message _msg_ matches the match rule.
86
+ def match(msg)
87
+ if @type
88
+ if {Message::SIGNAL => "signal", Message::METHOD_CALL => "method_call",
89
+ Message::METHOD_RETURN => "method_return",
90
+ Message::ERROR => "error"}[msg.message_type] != @type
91
+ return false
92
+ end
93
+ end
94
+ return false if @interface and @interface != msg.interface
95
+ return false if @member and @member != msg.member
96
+ return false if @path and @path != msg.path
97
+ true
98
+ end
99
+ end # class MatchRule
100
+ end # module D-Bus