yong-ruby-dbus 0.2.1

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.
@@ -0,0 +1,391 @@
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] != 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] != 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
253
+ @packet = ""
254
+ end
255
+
256
+ # Align the buffer with NULL (\0) bytes on a byte length of _a_.
257
+ def align(a)
258
+ case a
259
+ when 1
260
+ when 2, 4, 8
261
+ bits = a - 1
262
+ @packet = @packet.ljust(@packet.length + bits & ~bits, 0.chr)
263
+ else
264
+ raise "Unsupported alignment"
265
+ end
266
+ end
267
+
268
+ # Append the the string _str_ itself to the packet.
269
+ def append_string(str)
270
+ align(4)
271
+ @packet += [str.length].pack("L") + str + "\0"
272
+ end
273
+
274
+ # Append the the signature _signature_ itself to the packet.
275
+ def append_signature(str)
276
+ @packet += str.length.chr + str + "\0"
277
+ end
278
+
279
+ # Append the array type _type_ to the packet and allow for appending
280
+ # the child elements.
281
+ def array(type)
282
+ # Thanks to Peter Rullmann for this line
283
+ align(4)
284
+ sizeidx = @packet.size
285
+ @packet += "ABCD"
286
+ align(type.alignment)
287
+ contentidx = @packet.size
288
+ yield
289
+ sz = @packet.size - contentidx
290
+ raise InvalidPacketException if sz > 67108864
291
+ @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
292
+ end
293
+
294
+ # Align and allow for appending struct fields.
295
+ def struct
296
+ align(8)
297
+ yield
298
+ end
299
+
300
+ # Append a string of bytes without type.
301
+ def append_simple_string(s)
302
+ @packet += s + "\0"
303
+ end
304
+
305
+ # Append a value _val_ to the packet based on its _type_.
306
+ def append(type, val)
307
+ type = type.chr if type.kind_of?(Fixnum)
308
+ type = Type::Parser.new(type).parse[0] if type.kind_of?(String)
309
+ case type.sigtype
310
+ when Type::BYTE
311
+ @packet += val.chr
312
+ when Type::UINT32
313
+ align(4)
314
+ @packet += [val].pack("L")
315
+ when Type::UINT64
316
+ align(8)
317
+ @packet += [val].pack("Q")
318
+ when Type::INT64
319
+ align(8)
320
+ @packet += [val].pack("q")
321
+ when Type::INT32
322
+ align(4)
323
+ @packet += [val].pack("l")
324
+ when Type::UINT16
325
+ align(2)
326
+ @packet += [val].pack("S")
327
+ when Type::INT16
328
+ align(2)
329
+ @packet += [val].pack("s")
330
+ when Type::DOUBLE
331
+ align(8)
332
+ @packet += [val].pack("d")
333
+ when Type::BOOLEAN
334
+ align(4)
335
+ if val
336
+ @packet += [1].pack("L")
337
+ else
338
+ @packet += [0].pack("L")
339
+ end
340
+ when Type::OBJECT_PATH
341
+ append_string(val)
342
+ when Type::STRING
343
+ append_string(val)
344
+ when Type::SIGNATURE
345
+ append_signature(val)
346
+ when Type::VARIANT
347
+ if not val.kind_of?(Array)
348
+ raise TypeException
349
+ end
350
+ vartype, vardata = val
351
+ vartype = Type::Parser.new(vartype).parse[0] if vartype.kind_of?(String)
352
+ append_signature(vartype.to_s)
353
+ align(vartype.alignment)
354
+ sub = PacketMarshaller.new
355
+ sub.append(vartype, vardata)
356
+ @packet += sub.packet
357
+ when Type::ARRAY
358
+ if val.kind_of?(Hash)
359
+ raise TypeException if type.child.sigtype != Type::DICT_ENTRY
360
+ # Damn ruby rocks here
361
+ val = val.to_a
362
+ end
363
+ if not val.kind_of?(Array)
364
+ raise TypeException
365
+ end
366
+ array(type.child) do
367
+ val.each do |elem|
368
+ append(type.child, elem)
369
+ end
370
+ end
371
+ when Type::STRUCT, Type::DICT_ENTRY
372
+ raise TypeException if not val.kind_of?(Array)
373
+ if type.sigtype == Type::DICT_ENTRY and val.size != 2
374
+ raise TypeException
375
+ end
376
+ struct do
377
+ idx = 0
378
+ while val[idx] != nil
379
+ type.members.each do |subtype|
380
+ raise TypeException if val[idx] == nil
381
+ append(subtype, val[idx])
382
+ idx += 1
383
+ end
384
+ end
385
+ end
386
+ else
387
+ raise NotImplementedError
388
+ end
389
+ end # def append
390
+ end # class PacketMarshaller
391
+ end # module DBus
@@ -0,0 +1,98 @@
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
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
+ s.split(",").each do |eq|
60
+ if eq =~ /^(.*)='([^']*)'$/
61
+ name = $1
62
+ val = $1
63
+ if FILTERS.member?(name.to_sym)
64
+ method(name + "=").call(val)
65
+ else
66
+ raise MatchRuleException
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # Sets the match rule to filter for the given _signal_ and the
73
+ # given interface _intf_.
74
+ def from_signal(intf, signal)
75
+ signal = signal.name unless signal.is_a?(String)
76
+ self.type = "signal"
77
+ self.interface = intf.name
78
+ self.member = signal
79
+ self.path = intf.object.path
80
+ self
81
+ end
82
+
83
+ # Determines whether a message _msg_ matches the match rule.
84
+ def match(msg)
85
+ if @type
86
+ if {Message::SIGNAL => "signal", Message::METHOD_CALL => "method_call",
87
+ Message::METHOD_RETURN => "method_return",
88
+ Message::ERROR => "error"}[msg.message_type] != @type
89
+ return false
90
+ end
91
+ end
92
+ return false if @interface and @interface != msg.interface
93
+ return false if @member and @member != msg.member
94
+ return false if @path and @path != msg.path
95
+ true
96
+ end
97
+ end # class MatchRule
98
+ end # module D-Bus