ruby-dbus 0.16.0 → 0.18.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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +160 -0
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +106 -7
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/gdbus/gdbus +31 -24
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +1 -1
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +2 -1
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +1 -0
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +20 -15
  22. data/lib/dbus/bus.rb +123 -75
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  25. data/lib/dbus/data.rb +821 -0
  26. data/lib/dbus/emits_changed_signal.rb +83 -0
  27. data/lib/dbus/error.rb +4 -2
  28. data/lib/dbus/introspect.rb +132 -31
  29. data/lib/dbus/logger.rb +3 -1
  30. data/lib/dbus/marshall.rb +247 -296
  31. data/lib/dbus/matchrule.rb +16 -16
  32. data/lib/dbus/message.rb +44 -37
  33. data/lib/dbus/message_queue.rb +16 -10
  34. data/lib/dbus/object.rb +358 -24
  35. data/lib/dbus/object_path.rb +11 -6
  36. data/lib/dbus/proxy_object.rb +22 -1
  37. data/lib/dbus/proxy_object_factory.rb +13 -7
  38. data/lib/dbus/proxy_object_interface.rb +63 -30
  39. data/lib/dbus/raw_message.rb +91 -0
  40. data/lib/dbus/type.rb +318 -86
  41. data/lib/dbus/xml.rb +32 -17
  42. data/lib/dbus.rb +14 -7
  43. data/ruby-dbus.gemspec +7 -3
  44. data/spec/async_spec.rb +2 -0
  45. data/spec/binding_spec.rb +2 -0
  46. data/spec/bus_and_xml_backend_spec.rb +2 -0
  47. data/spec/bus_driver_spec.rb +2 -0
  48. data/spec/bus_name_spec.rb +3 -1
  49. data/spec/bus_spec.rb +2 -0
  50. data/spec/byte_array_spec.rb +2 -0
  51. data/spec/client_robustness_spec.rb +4 -2
  52. data/spec/data/marshall.yaml +1667 -0
  53. data/spec/data_spec.rb +673 -0
  54. data/spec/emits_changed_signal_spec.rb +58 -0
  55. data/spec/err_msg_spec.rb +2 -0
  56. data/spec/introspect_xml_parser_spec.rb +2 -0
  57. data/spec/introspection_spec.rb +2 -0
  58. data/spec/main_loop_spec.rb +3 -1
  59. data/spec/node_spec.rb +23 -0
  60. data/spec/object_path_spec.rb +3 -0
  61. data/spec/object_spec.rb +138 -0
  62. data/spec/packet_marshaller_spec.rb +41 -0
  63. data/spec/packet_unmarshaller_spec.rb +248 -0
  64. data/spec/property_spec.rb +192 -5
  65. data/spec/proxy_object_spec.rb +2 -0
  66. data/spec/server_robustness_spec.rb +2 -0
  67. data/spec/server_spec.rb +2 -0
  68. data/spec/service_newapi.rb +70 -70
  69. data/spec/session_bus_spec.rb +3 -1
  70. data/spec/session_bus_spec_manual.rb +2 -0
  71. data/spec/signal_spec.rb +5 -3
  72. data/spec/spec_helper.rb +37 -9
  73. data/spec/thread_safety_spec.rb +2 -0
  74. data/spec/tools/dbus-limited-session.conf +4 -0
  75. data/spec/type_spec.rb +214 -6
  76. data/spec/value_spec.rb +16 -1
  77. data/spec/variant_spec.rb +4 -2
  78. data/spec/zzz_quit_spec.rb +16 -0
  79. metadata +34 -8
data/lib/dbus/marshall.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus.rb - Module containing the low-level D-Bus implementation
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -10,6 +12,8 @@
10
12
 
11
13
  require "socket"
12
14
 
15
+ require_relative "../dbus/type"
16
+
13
17
  # = D-Bus main module
14
18
  #
15
19
  # Module containing all the D-Bus modules and classes.
@@ -21,211 +25,134 @@ module DBus
21
25
  # = D-Bus packet unmarshaller class
22
26
  #
23
27
  # Class that handles the conversion (unmarshalling) of payload data
24
- # to Array.
28
+ # to #{::Object}s (in **plain** mode) or to {Data::Base} (in **exact** mode)
29
+ #
30
+ # Spelling note: this codebase always uses a double L
31
+ # in the "marshall" word and its inflections.
25
32
  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_.
33
+ # Create a new unmarshaller for the given data *buffer*.
34
+ # @param buffer [String]
35
+ # @param endianness [:little,:big]
34
36
  def initialize(buffer, endianness)
35
- @buffy = buffer.dup
36
- @endianness = endianness
37
- if @endianness == BIG_END
38
- @uint32 = "N"
39
- @uint16 = "n"
40
- @double = "G"
41
- elsif @endianness == LIL_END
42
- @uint32 = "V"
43
- @uint16 = "v"
44
- @double = "E"
45
- else
46
- raise InvalidPacketException, "Incorrect endianness #{@endianness}"
47
- end
48
- @idx = 0
37
+ # TODO: this dup can be avoided if we can prove
38
+ # that an IncompleteBufferException leaves the original *buffer* intact
39
+ buffer = buffer.dup
40
+ @raw_msg = RawMessage.new(buffer, endianness)
49
41
  end
50
42
 
51
43
  # Unmarshall the buffer for a given _signature_ and length _len_.
52
- # Return an array of unmarshalled objects
53
- def unmarshall(signature, len = nil)
54
- if !len.nil?
55
- if @buffy.bytesize < @idx + len
56
- raise IncompleteBufferException
57
- end
58
- end
44
+ # Return an array of unmarshalled objects.
45
+ # @param signature [Signature]
46
+ # @param len [Integer,nil] if given, and there is not enough data
47
+ # in the buffer, raise {IncompleteBufferException}
48
+ # @param mode [:plain,:exact]
49
+ # @return [Array<::Object,DBus::Data::Base>]
50
+ # Objects in `:plain` mode, {DBus::Data::Base} in `:exact` mode
51
+ # The array size corresponds to the number of types in *signature*.
52
+ # @raise IncompleteBufferException
53
+ # @raise InvalidPacketException
54
+ def unmarshall(signature, len = nil, mode: :plain)
55
+ @raw_msg.want!(len) if len
56
+
59
57
  sigtree = Type::Parser.new(signature).parse
60
58
  ret = []
61
59
  sigtree.each do |elem|
62
- ret << do_parse(elem)
60
+ ret << do_parse(elem, mode: mode)
63
61
  end
64
62
  ret
65
63
  end
66
64
 
67
- # Align the pointer index on a byte index of _a_, where a
68
- # must be 1, 2, 4 or 8.
69
- def align(a)
70
- case a
71
- when 1
72
- nil
73
- when 2, 4, 8
74
- bits = a - 1
75
- @idx = @idx + bits & ~bits
76
- raise IncompleteBufferException if @idx > @buffy.bytesize
77
- else
78
- raise "Unsupported alignment #{a}"
79
- end
65
+ # after the headers, the body starts 8-aligned
66
+ def align_body
67
+ @raw_msg.align(8)
80
68
  end
81
69
 
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 read(nbytes)
89
- raise IncompleteBufferException if @idx + nbytes > @buffy.bytesize
90
- ret = @buffy.slice(@idx, nbytes)
91
- @idx += nbytes
92
- ret
70
+ # @return [Integer]
71
+ def consumed_size
72
+ @raw_msg.pos
93
73
  end
94
74
 
95
- # Read the string length and string itself from the buffer.
96
- # Return the string.
97
- def read_string
98
- align(4)
99
- str_sz = read(4).unpack(@uint32)[0]
100
- ret = @buffy.slice(@idx, str_sz)
101
- raise IncompleteBufferException if @idx + str_sz + 1 > @buffy.bytesize
102
- @idx += str_sz
103
- if @buffy[@idx].ord != 0
104
- raise InvalidPacketException, "String is not nul-terminated"
105
- end
106
- @idx += 1
107
- # no exception, see check above
108
- ret
109
- end
75
+ private
110
76
 
111
- # Read the signature length and signature itself from the buffer.
112
- # Return the signature.
113
- def read_signature
114
- str_sz = read(1).unpack("C")[0]
115
- ret = @buffy.slice(@idx, str_sz)
116
- raise IncompleteBufferException if @idx + str_sz + 1 >= @buffy.bytesize
117
- @idx += str_sz
118
- if @buffy[@idx].ord != 0
119
- raise InvalidPacketException, "Type is not nul-terminated"
120
- end
121
- @idx += 1
122
- # no exception, see check above
123
- ret
77
+ # @param data_class [Class] a subclass of Data::Base (specific?)
78
+ # @return [::Integer,::Float]
79
+ def aligned_read_value(data_class)
80
+ @raw_msg.align(data_class.alignment)
81
+ bytes = @raw_msg.read(data_class.alignment)
82
+ bytes.unpack1(data_class.format[@raw_msg.endianness])
124
83
  end
125
84
 
126
85
  # Based on the _signature_ type, retrieve a packet from the buffer
127
86
  # and return it.
128
- def do_parse(signature)
87
+ # @param signature [Type]
88
+ # @param mode [:plain,:exact]
89
+ # @return [Data::Base]
90
+ def do_parse(signature, mode: :plain)
91
+ # FIXME: better naming for packet vs value
129
92
  packet = nil
130
- case signature.sigtype
131
- when Type::BYTE
132
- packet = read(1).unpack("C")[0]
133
- when Type::UINT16
134
- align(2)
135
- packet = read(2).unpack(@uint16)[0]
136
- when Type::INT16
137
- align(2)
138
- packet = read(2).unpack(@uint16)[0]
139
- if (packet & 0x8000) != 0
140
- packet -= 0x10000
141
- end
142
- when Type::UINT32, Type::UNIX_FD
143
- align(4)
144
- packet = read(4).unpack(@uint32)[0]
145
- when Type::INT32
146
- align(4)
147
- packet = read(4).unpack(@uint32)[0]
148
- if (packet & 0x80000000) != 0
149
- packet -= 0x100000000
150
- end
151
- when Type::UINT64
152
- align(8)
153
- packet_l = read(4).unpack(@uint32)[0]
154
- packet_h = read(4).unpack(@uint32)[0]
155
- packet = if @endianness == LIL_END
156
- packet_l + packet_h * 2**32
157
- else
158
- packet_l * 2**32 + packet_h
159
- end
160
- when Type::INT64
161
- align(8)
162
- packet_l = read(4).unpack(@uint32)[0]
163
- packet_h = read(4).unpack(@uint32)[0]
164
- packet = if @endianness == LIL_END
165
- packet_l + packet_h * 2**32
166
- else
167
- packet_l * 2**32 + packet_h
168
- end
169
- if (packet & 0x8000000000000000) != 0
170
- packet -= 0x10000000000000000
171
- end
172
- when Type::DOUBLE
173
- align(8)
174
- packet = read(8).unpack(@double)[0]
175
- when Type::BOOLEAN
176
- align(4)
177
- v = read(4).unpack(@uint32)[0]
178
- raise InvalidPacketException if ![0, 1].member?(v)
179
- packet = (v == 1)
180
- when Type::ARRAY
181
- align(4)
182
- # checks please
183
- array_sz = read(4).unpack(@uint32)[0]
184
- raise InvalidPacketException if array_sz > 67_108_864
185
-
186
- align(signature.child.alignment)
187
- raise IncompleteBufferException if @idx + array_sz > @buffy.bytesize
188
-
189
- packet = []
190
- start_idx = @idx
191
- while @idx - start_idx < array_sz
192
- packet << do_parse(signature.child)
193
- end
93
+ data_class = Data::BY_TYPE_CODE[signature.sigtype]
194
94
 
195
- if signature.child.sigtype == Type::DICT_ENTRY
196
- packet = Hash[packet]
197
- end
198
- when Type::STRUCT
199
- align(8)
200
- packet = []
201
- signature.members.each do |elem|
202
- packet << do_parse(elem)
203
- end
204
- when Type::VARIANT
205
- string = read_signature
206
- # error checking please
207
- sig = Type::Parser.new(string).parse[0]
208
- align(sig.alignment)
209
- packet = do_parse(sig)
210
- when Type::OBJECT_PATH
211
- packet = read_string
212
- when Type::STRING
213
- packet = read_string
214
- packet.force_encoding("UTF-8")
215
- when Type::SIGNATURE
216
- packet = read_signature
217
- when Type::DICT_ENTRY
218
- align(8)
219
- key = do_parse(signature.members[0])
220
- value = do_parse(signature.members[1])
221
- packet = [key, value]
222
- else
95
+ if data_class.nil?
223
96
  raise NotImplementedError,
224
97
  "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})"
225
98
  end
99
+
100
+ if data_class.fixed?
101
+ value = aligned_read_value(data_class)
102
+ packet = data_class.from_raw(value, mode: mode)
103
+ elsif data_class.basic?
104
+ size = aligned_read_value(data_class.size_class)
105
+ # @raw_msg.align(data_class.alignment)
106
+ # ^ is not necessary because we've just read a suitably-aligned *size*
107
+ value = @raw_msg.read(size)
108
+ nul = @raw_msg.read(1)
109
+ if nul != "\u0000"
110
+ raise InvalidPacketException, "#{data_class} is not NUL-terminated"
111
+ end
112
+
113
+ packet = data_class.from_raw(value, mode: mode)
114
+ else
115
+ @raw_msg.align(data_class.alignment)
116
+ case signature.sigtype
117
+ when Type::STRUCT, Type::DICT_ENTRY
118
+ values = signature.members.map do |child_sig|
119
+ do_parse(child_sig, mode: mode)
120
+ end
121
+ packet = data_class.from_items(values, mode: mode, type: signature)
122
+
123
+ when Type::VARIANT
124
+ data_sig = do_parse(Data::Signature.type, mode: :exact) # -> Data::Signature
125
+ types = Type::Parser.new(data_sig.value).parse # -> Array<Type>
126
+ unless types.size == 1
127
+ raise InvalidPacketException, "VARIANT must contain 1 value, #{types.size} found"
128
+ end
129
+
130
+ type = types.first
131
+ value = do_parse(type, mode: mode)
132
+ packet = data_class.from_items(value, mode: mode, member_type: type)
133
+
134
+ when Type::ARRAY
135
+ array_bytes = aligned_read_value(Data::UInt32)
136
+ if array_bytes > 67_108_864
137
+ raise InvalidPacketException, "ARRAY body longer than 64MiB"
138
+ end
139
+
140
+ # needed here because of empty arrays
141
+ @raw_msg.align(signature.child.alignment)
142
+
143
+ items = []
144
+ end_pos = @raw_msg.pos + array_bytes
145
+ while @raw_msg.pos < end_pos
146
+ item = do_parse(signature.child, mode: mode)
147
+ items << item
148
+ end
149
+ is_hash = signature.child.sigtype == Type::DICT_ENTRY
150
+ packet = data_class.from_items(items, mode: mode, type: signature, hash: is_hash)
151
+ end
152
+ end
226
153
  packet
227
- end # def do_parse
228
- end # class PacketUnmarshaller
154
+ end
155
+ end
229
156
 
230
157
  # D-Bus packet marshaller class
231
158
  #
@@ -234,40 +161,35 @@ module DBus
234
161
  class PacketMarshaller
235
162
  # The current or result packet.
236
163
  # FIXME: allow access only when marshalling is finished
164
+ # @return [String]
237
165
  attr_reader :packet
238
166
 
167
+ # @return [:little,:big]
168
+ attr_reader :endianness
169
+
239
170
  # Create a new marshaller, setting the current packet to the
240
171
  # empty packet.
241
- def initialize(offset = 0)
172
+ def initialize(offset = 0, endianness: HOST_ENDIANNESS)
173
+ @endianness = endianness
242
174
  @packet = ""
243
175
  @offset = offset # for correct alignment of nested marshallers
244
176
  end
245
177
 
246
- # Round _n_ up to the specified power of two, _a_
247
- def num_align(n, a)
248
- case a
178
+ # Round _num_ up to the specified power of two, _alignment_
179
+ def num_align(num, alignment)
180
+ case alignment
249
181
  when 1, 2, 4, 8
250
- bits = a - 1
251
- n + bits & ~bits
182
+ bits = alignment - 1
183
+ num + bits & ~bits
252
184
  else
253
- raise "Unsupported alignment"
185
+ raise ArgumentError, "Unsupported alignment #{alignment}"
254
186
  end
255
187
  end
256
188
 
257
- # Align the buffer with NULL (\0) bytes on a byte length of _a_.
258
- def align(a)
259
- @packet = @packet.ljust(num_align(@offset + @packet.bytesize, a) - @offset, 0.chr)
260
- end
261
-
262
- # Append the the string _str_ itself to the packet.
263
- def append_string(str)
264
- align(4)
265
- @packet += [str.bytesize].pack("L") + [str].pack("Z*")
266
- end
267
-
268
- # Append the the signature _signature_ itself to the packet.
269
- def append_signature(str)
270
- @packet += str.bytesize.chr + str + "\0"
189
+ # Align the buffer with NULL (\0) bytes on a byte length of _alignment_.
190
+ def align(alignment)
191
+ pad_count = num_align(@offset + @packet.bytesize, alignment) - @offset
192
+ @packet = @packet.ljust(pad_count, 0.chr)
271
193
  end
272
194
 
273
195
  # Append the array type _type_ to the packet and allow for appending
@@ -282,7 +204,9 @@ module DBus
282
204
  yield
283
205
  sz = @packet.bytesize - contentidx
284
206
  raise InvalidPacketException if sz > 67_108_864
285
- @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
207
+
208
+ sz_data = Data::UInt32.new(sz)
209
+ @packet[sizeidx...sizeidx + 4] = sz_data.marshall(endianness)
286
210
  end
287
211
 
288
212
  # Align and allow for appending struct fields.
@@ -294,110 +218,129 @@ module DBus
294
218
  # Append a value _val_ to the packet based on its _type_.
295
219
  #
296
220
  # Host native endianness is used, declared in Message#marshall
221
+ #
222
+ # @param type [SingleCompleteType] (or Integer or {Type})
223
+ # @param val [::Object]
297
224
  def append(type, val)
298
225
  raise TypeException, "Cannot send nil" if val.nil?
299
226
 
300
227
  type = type.chr if type.is_a?(Integer)
301
228
  type = Type::Parser.new(type).parse[0] if type.is_a?(String)
302
- case type.sigtype
303
- when Type::BYTE
304
- @packet += val.chr
305
- when Type::UINT32, Type::UNIX_FD
306
- align(4)
307
- @packet += [val].pack("L")
308
- when Type::UINT64
309
- align(8)
310
- @packet += [val].pack("Q")
311
- when Type::INT64
312
- align(8)
313
- @packet += [val].pack("q")
314
- when Type::INT32
315
- align(4)
316
- @packet += [val].pack("l")
317
- when Type::UINT16
318
- align(2)
319
- @packet += [val].pack("S")
320
- when Type::INT16
321
- align(2)
322
- @packet += [val].pack("s")
323
- when Type::DOUBLE
324
- align(8)
325
- @packet += [val].pack("d")
326
- when Type::BOOLEAN
327
- align(4)
328
- @packet += if val
329
- [1].pack("L")
330
- else
331
- [0].pack("L")
332
- end
333
- when Type::OBJECT_PATH
334
- append_string(val)
335
- when Type::STRING
336
- append_string(val)
337
- when Type::SIGNATURE
338
- append_signature(val)
339
- when Type::VARIANT
340
- vartype = nil
341
- if val.is_a?(Array) && val.size == 2
342
- if val[0].is_a?(DBus::Type::Type)
343
- vartype, vardata = val
344
- elsif val[0].is_a?(String)
345
- begin
346
- parsed = Type::Parser.new(val[0]).parse
347
- vartype = parsed[0] if parsed.size == 1
348
- vardata = val[1]
349
- rescue Type::SignatureException
350
- # no assignment
229
+ # type is [Type] now
230
+ data_class = Data::BY_TYPE_CODE[type.sigtype]
231
+ if data_class.nil?
232
+ raise NotImplementedError,
233
+ "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
234
+ end
235
+
236
+ if data_class.fixed?
237
+ align(data_class.alignment)
238
+ data = data_class.new(val)
239
+ @packet += data.marshall(endianness)
240
+ elsif data_class.basic?
241
+ val = val.value if val.is_a?(Data::Basic)
242
+ align(data_class.size_class.alignment)
243
+ size_data = data_class.size_class.new(val.bytesize)
244
+ @packet += size_data.marshall(endianness)
245
+ # Z* makes a binary string, as opposed to interpolation
246
+ @packet += [val].pack("Z*")
247
+ else
248
+ case type.sigtype
249
+
250
+ when Type::VARIANT
251
+ append_variant(val)
252
+ when Type::ARRAY
253
+ val = val.exact_value if val.is_a?(Data::Array)
254
+ append_array(type.child, val)
255
+ when Type::STRUCT, Type::DICT_ENTRY
256
+ val = val.exact_value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry)
257
+ unless val.is_a?(Array) || val.is_a?(Struct)
258
+ type_name = Type::TYPE_MAPPING[type.sigtype].first
259
+ raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}"
260
+ end
261
+
262
+ if type.sigtype == Type::DICT_ENTRY && val.size != 2
263
+ raise TypeException, "DICT_ENTRY expects a pair"
264
+ end
265
+
266
+ if type.members.size != val.size
267
+ type_name = Type::TYPE_MAPPING[type.sigtype].first
268
+ raise TypeException, "#{type_name} has #{val.size} elements but type info for #{type.members.size}"
269
+ end
270
+
271
+ struct do
272
+ type.members.zip(val).each do |t, v|
273
+ append(t, v)
351
274
  end
352
275
  end
276
+ else
277
+ raise NotImplementedError,
278
+ "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
353
279
  end
354
- if vartype.nil?
355
- vartype, vardata = PacketMarshaller.make_variant(val)
356
- vartype = Type::Parser.new(vartype).parse[0]
357
- end
280
+ end
281
+ end
358
282
 
359
- append_signature(vartype.to_s)
360
- align(vartype.alignment)
361
- sub = PacketMarshaller.new(@offset + @packet.bytesize)
362
- sub.append(vartype, vardata)
363
- @packet += sub.packet
364
- when Type::ARRAY
365
- if val.is_a?(Hash)
366
- raise TypeException, "Expected an Array but got a Hash" if type.child.sigtype != Type::DICT_ENTRY
367
- # Damn ruby rocks here
368
- val = val.to_a
369
- end
370
- # If string is recieved and ay is expected, explode the string
371
- if val.is_a?(String) && type.child.sigtype == Type::BYTE
372
- val = val.bytes
373
- end
374
- if !val.is_a?(Enumerable)
375
- raise TypeException, "Expected an Enumerable of #{type.child.inspect} but got a #{val.class}"
376
- end
377
- array(type.child) do
378
- val.each do |elem|
379
- append(type.child, elem)
283
+ def append_variant(val)
284
+ vartype = nil
285
+ if val.is_a?(DBus::Data::Variant)
286
+ vartype = val.member_type
287
+ vardata = val.exact_value
288
+ elsif val.is_a?(DBus::Data::Container)
289
+ vartype = val.type
290
+ vardata = val.exact_value
291
+ elsif val.is_a?(DBus::Data::Base)
292
+ vartype = val.type
293
+ vardata = val.value
294
+ elsif val.is_a?(Array) && val.size == 2
295
+ case val[0]
296
+ when Type
297
+ vartype, vardata = val
298
+ # Ambiguous but easy to use, because Type
299
+ # cannot construct "as" "a{sv}" easily
300
+ when String
301
+ begin
302
+ parsed = Type::Parser.new(val[0]).parse
303
+ vartype = parsed[0] if parsed.size == 1
304
+ vardata = val[1]
305
+ rescue Type::SignatureException
306
+ # no assignment
380
307
  end
381
308
  end
382
- when Type::STRUCT, Type::DICT_ENTRY
383
- # TODO: use duck typing, val.respond_to?
384
- raise TypeException, "Struct/DE expects an Array" if !val.is_a?(Array)
385
- if type.sigtype == Type::DICT_ENTRY && val.size != 2
386
- raise TypeException, "Dict entry expects a pair"
387
- end
388
- if type.members.size != val.size
389
- raise TypeException, "Struct/DE has #{val.size} elements but type info for #{type.members.size}"
390
- end
391
- struct do
392
- type.members.zip(val).each do |t, v|
393
- append(t, v)
394
- end
309
+ end
310
+ if vartype.nil?
311
+ vartype, vardata = PacketMarshaller.make_variant(val)
312
+ vartype = Type::Parser.new(vartype).parse[0]
313
+ end
314
+
315
+ append(Data::Signature.type, vartype.to_s)
316
+ align(vartype.alignment)
317
+ sub = PacketMarshaller.new(@offset + @packet.bytesize, endianness: endianness)
318
+ sub.append(vartype, vardata)
319
+ @packet += sub.packet
320
+ end
321
+
322
+ # @param child_type [Type]
323
+ def append_array(child_type, val)
324
+ if val.is_a?(Hash)
325
+ raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY
326
+
327
+ # Damn ruby rocks here
328
+ val = val.to_a
329
+ end
330
+ # If string is recieved and ay is expected, explode the string
331
+ if val.is_a?(String) && child_type.sigtype == Type::BYTE
332
+ val = val.bytes
333
+ end
334
+ if !val.is_a?(Enumerable)
335
+ raise TypeException, "Expected an Enumerable of #{child_type.inspect} but got a #{val.class}"
336
+ end
337
+
338
+ array(child_type) do
339
+ val.each do |elem|
340
+ append(child_type, elem)
395
341
  end
396
- else
397
- raise NotImplementedError,
398
- "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
399
342
  end
400
- end # def append
343
+ end
401
344
 
402
345
  # Make a [signature, value] pair for a variant
403
346
  def self.make_variant(value)
@@ -417,17 +360,25 @@ module DBus
417
360
  elsif value.is_a? Hash
418
361
  h = {}
419
362
  value.each_key { |k| h[k] = make_variant(value[k]) }
420
- ["a{sv}", h]
363
+ key_type = if value.empty?
364
+ "s"
365
+ else
366
+ t, = make_variant(value.first.first)
367
+ t
368
+ end
369
+ ["a{#{key_type}v}", h]
421
370
  elsif value.respond_to? :to_str
422
371
  ["s", value.to_str]
423
372
  elsif value.respond_to? :to_int
424
373
  i = value.to_int
425
- if -2_147_483_648 <= i && i < 2_147_483_648
374
+ if Data::Int32.range.cover?(i)
426
375
  ["i", i]
427
- else
376
+ elsif Data::Int64.range.cover?(i)
428
377
  ["x", i]
378
+ else
379
+ ["t", i]
429
380
  end
430
381
  end
431
382
  end
432
- end # class PacketMarshaller
433
- end # module DBus
383
+ end
384
+ end