ruby-dbus 0.16.0 → 0.18.0.beta2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +46 -0
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +94 -4
  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 +126 -74
  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 +725 -0
  26. data/lib/dbus/error.rb +4 -2
  27. data/lib/dbus/introspect.rb +91 -30
  28. data/lib/dbus/logger.rb +3 -1
  29. data/lib/dbus/marshall.rb +228 -294
  30. data/lib/dbus/matchrule.rb +16 -16
  31. data/lib/dbus/message.rb +44 -37
  32. data/lib/dbus/message_queue.rb +16 -10
  33. data/lib/dbus/object.rb +296 -24
  34. data/lib/dbus/object_path.rb +11 -6
  35. data/lib/dbus/proxy_object.rb +22 -1
  36. data/lib/dbus/proxy_object_factory.rb +11 -7
  37. data/lib/dbus/proxy_object_interface.rb +26 -21
  38. data/lib/dbus/raw_message.rb +91 -0
  39. data/lib/dbus/type.rb +182 -80
  40. data/lib/dbus/xml.rb +28 -17
  41. data/lib/dbus.rb +13 -7
  42. data/ruby-dbus.gemspec +7 -3
  43. data/spec/async_spec.rb +2 -0
  44. data/spec/binding_spec.rb +2 -0
  45. data/spec/bus_and_xml_backend_spec.rb +2 -0
  46. data/spec/bus_driver_spec.rb +2 -0
  47. data/spec/bus_name_spec.rb +3 -1
  48. data/spec/bus_spec.rb +2 -0
  49. data/spec/byte_array_spec.rb +2 -0
  50. data/spec/client_robustness_spec.rb +4 -2
  51. data/spec/data/marshall.yaml +1639 -0
  52. data/spec/data_spec.rb +298 -0
  53. data/spec/err_msg_spec.rb +2 -0
  54. data/spec/introspect_xml_parser_spec.rb +2 -0
  55. data/spec/introspection_spec.rb +2 -0
  56. data/spec/main_loop_spec.rb +3 -1
  57. data/spec/node_spec.rb +23 -0
  58. data/spec/object_path_spec.rb +3 -0
  59. data/spec/packet_marshaller_spec.rb +34 -0
  60. data/spec/packet_unmarshaller_spec.rb +262 -0
  61. data/spec/property_spec.rb +88 -5
  62. data/spec/proxy_object_spec.rb +2 -0
  63. data/spec/server_robustness_spec.rb +2 -0
  64. data/spec/server_spec.rb +2 -0
  65. data/spec/service_newapi.rb +39 -70
  66. data/spec/session_bus_spec.rb +3 -1
  67. data/spec/session_bus_spec_manual.rb +2 -0
  68. data/spec/signal_spec.rb +5 -3
  69. data/spec/spec_helper.rb +35 -9
  70. data/spec/thread_safety_spec.rb +2 -0
  71. data/spec/tools/dbus-limited-session.conf +4 -0
  72. data/spec/type_spec.rb +69 -6
  73. data/spec/value_spec.rb +16 -1
  74. data/spec/variant_spec.rb +4 -2
  75. metadata +32 -10
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,132 @@ 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
+ value = @raw_msg.read(size)
106
+ nul = @raw_msg.read(1)
107
+ if nul != "\u0000"
108
+ raise InvalidPacketException, "#{data_class} is not NUL-terminated"
109
+ end
110
+
111
+ packet = data_class.from_raw(value, mode: mode)
112
+ else
113
+ @raw_msg.align(data_class.alignment)
114
+ case signature.sigtype
115
+ when Type::STRUCT, Type::DICT_ENTRY
116
+ values = signature.members.map do |child_sig|
117
+ do_parse(child_sig, mode: mode)
118
+ end
119
+ packet = data_class.from_items(values, mode: mode, member_types: signature.members)
120
+
121
+ when Type::VARIANT
122
+ data_sig = do_parse(Data::Signature.type, mode: :exact) # -> Data::Signature
123
+ types = Type::Parser.new(data_sig.value).parse # -> Array<Type>
124
+ unless types.size == 1
125
+ raise InvalidPacketException, "VARIANT must contain 1 value, #{types.size} found"
126
+ end
127
+
128
+ type = types.first
129
+ value = do_parse(type, mode: mode)
130
+ packet = data_class.from_items(value, mode: mode, member_type: type)
131
+
132
+ when Type::ARRAY
133
+ array_bytes = aligned_read_value(Data::UInt32)
134
+ if array_bytes > 67_108_864
135
+ raise InvalidPacketException, "ARRAY body longer than 64MiB"
136
+ end
137
+
138
+ # needed here because of empty arrays
139
+ @raw_msg.align(signature.child.alignment)
140
+
141
+ items = []
142
+ end_pos = @raw_msg.pos + array_bytes
143
+ while @raw_msg.pos < end_pos
144
+ item = do_parse(signature.child, mode: mode)
145
+ items << item
146
+ end
147
+ is_hash = signature.child.sigtype == Type::DICT_ENTRY
148
+ packet = data_class.from_items(items, mode: mode, member_type: signature.child, hash: is_hash)
149
+ end
150
+ end
226
151
  packet
227
- end # def do_parse
228
- end # class PacketUnmarshaller
152
+ end
153
+ end
229
154
 
230
155
  # D-Bus packet marshaller class
231
156
  #
@@ -234,40 +159,35 @@ module DBus
234
159
  class PacketMarshaller
235
160
  # The current or result packet.
236
161
  # FIXME: allow access only when marshalling is finished
162
+ # @return [String]
237
163
  attr_reader :packet
238
164
 
165
+ # @return [:little,:big]
166
+ attr_reader :endianness
167
+
239
168
  # Create a new marshaller, setting the current packet to the
240
169
  # empty packet.
241
- def initialize(offset = 0)
170
+ def initialize(offset = 0, endianness: HOST_ENDIANNESS)
171
+ @endianness = endianness
242
172
  @packet = ""
243
173
  @offset = offset # for correct alignment of nested marshallers
244
174
  end
245
175
 
246
- # Round _n_ up to the specified power of two, _a_
247
- def num_align(n, a)
248
- case a
176
+ # Round _num_ up to the specified power of two, _alignment_
177
+ def num_align(num, alignment)
178
+ case alignment
249
179
  when 1, 2, 4, 8
250
- bits = a - 1
251
- n + bits & ~bits
180
+ bits = alignment - 1
181
+ num + bits & ~bits
252
182
  else
253
- raise "Unsupported alignment"
183
+ raise ArgumentError, "Unsupported alignment #{alignment}"
254
184
  end
255
185
  end
256
186
 
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"
187
+ # Align the buffer with NULL (\0) bytes on a byte length of _alignment_.
188
+ def align(alignment)
189
+ pad_count = num_align(@offset + @packet.bytesize, alignment) - @offset
190
+ @packet = @packet.ljust(pad_count, 0.chr)
271
191
  end
272
192
 
273
193
  # Append the array type _type_ to the packet and allow for appending
@@ -282,7 +202,9 @@ module DBus
282
202
  yield
283
203
  sz = @packet.bytesize - contentidx
284
204
  raise InvalidPacketException if sz > 67_108_864
285
- @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
205
+
206
+ sz_data = Data::UInt32.new(sz)
207
+ @packet[sizeidx...sizeidx + 4] = sz_data.marshall(endianness)
286
208
  end
287
209
 
288
210
  # Align and allow for appending struct fields.
@@ -294,110 +216,122 @@ module DBus
294
216
  # Append a value _val_ to the packet based on its _type_.
295
217
  #
296
218
  # Host native endianness is used, declared in Message#marshall
219
+ #
220
+ # @param type [SingleCompleteType] (or Integer or {Type})
221
+ # @param val [::Object]
297
222
  def append(type, val)
298
223
  raise TypeException, "Cannot send nil" if val.nil?
299
224
 
300
225
  type = type.chr if type.is_a?(Integer)
301
226
  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
227
+ # type is [Type] now
228
+ data_class = Data::BY_TYPE_CODE[type.sigtype]
229
+ if data_class.nil?
230
+ raise NotImplementedError,
231
+ "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
232
+ end
233
+
234
+ if data_class.fixed?
235
+ align(data_class.alignment)
236
+ data = data_class.new(val)
237
+ @packet += data.marshall(endianness)
238
+ elsif data_class.basic?
239
+ val = val.value if val.is_a?(Data::Basic)
240
+ align(data_class.size_class.alignment)
241
+ size_data = data_class.size_class.new(val.bytesize)
242
+ @packet += size_data.marshall(endianness)
243
+ # Z* makes a binary string, as opposed to interpolation
244
+ @packet += [val].pack("Z*")
245
+ else
246
+ case type.sigtype
247
+
248
+ when Type::VARIANT
249
+ append_variant(val)
250
+ when Type::ARRAY
251
+ append_array(type.child, val)
252
+ when Type::STRUCT, Type::DICT_ENTRY
253
+ val = val.value if val.is_a?(Data::Struct)
254
+ unless val.is_a?(Array) || val.is_a?(Struct)
255
+ type_name = Type::TYPE_MAPPING[type.sigtype].first
256
+ raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}"
257
+ end
258
+
259
+ if type.sigtype == Type::DICT_ENTRY && val.size != 2
260
+ raise TypeException, "DICT_ENTRY expects a pair"
261
+ end
262
+
263
+ if type.members.size != val.size
264
+ type_name = Type::TYPE_MAPPING[type.sigtype].first
265
+ raise TypeException, "#{type_name} has #{val.size} elements but type info for #{type.members.size}"
266
+ end
267
+
268
+ struct do
269
+ type.members.zip(val).each do |t, v|
270
+ append(t, v)
351
271
  end
352
272
  end
273
+ else
274
+ raise NotImplementedError,
275
+ "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
353
276
  end
354
- if vartype.nil?
355
- vartype, vardata = PacketMarshaller.make_variant(val)
356
- vartype = Type::Parser.new(vartype).parse[0]
357
- end
277
+ end
278
+ end
358
279
 
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)
280
+ def append_variant(val)
281
+ vartype = nil
282
+ if val.is_a?(DBus::Data::Base)
283
+ vartype = val.type # FIXME: box or unbox another variant?
284
+ vardata = val.value
285
+ elsif val.is_a?(Array) && val.size == 2
286
+ case val[0]
287
+ when Type
288
+ vartype, vardata = val
289
+ # Ambiguous but easy to use, because Type
290
+ # cannot construct "as" "a{sv}" easily
291
+ when String
292
+ begin
293
+ parsed = Type::Parser.new(val[0]).parse
294
+ vartype = parsed[0] if parsed.size == 1
295
+ vardata = val[1]
296
+ rescue Type::SignatureException
297
+ # no assignment
380
298
  end
381
299
  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
300
+ end
301
+ if vartype.nil?
302
+ vartype, vardata = PacketMarshaller.make_variant(val)
303
+ vartype = Type::Parser.new(vartype).parse[0]
304
+ end
305
+
306
+ append(Data::Signature.type, vartype.to_s)
307
+ align(vartype.alignment)
308
+ sub = PacketMarshaller.new(@offset + @packet.bytesize, endianness: endianness)
309
+ sub.append(vartype, vardata)
310
+ @packet += sub.packet
311
+ end
312
+
313
+ # @param child_type [Type]
314
+ def append_array(child_type, val)
315
+ if val.is_a?(Hash)
316
+ raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY
317
+
318
+ # Damn ruby rocks here
319
+ val = val.to_a
320
+ end
321
+ # If string is recieved and ay is expected, explode the string
322
+ if val.is_a?(String) && child_type.sigtype == Type::BYTE
323
+ val = val.bytes
324
+ end
325
+ if !val.is_a?(Enumerable)
326
+ raise TypeException, "Expected an Enumerable of #{child_type.inspect} but got a #{val.class}"
327
+ end
328
+
329
+ array(child_type) do
330
+ val.each do |elem|
331
+ append(child_type, elem)
395
332
  end
396
- else
397
- raise NotImplementedError,
398
- "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
399
333
  end
400
- end # def append
334
+ end
401
335
 
402
336
  # Make a [signature, value] pair for a variant
403
337
  def self.make_variant(value)
@@ -422,12 +356,12 @@ module DBus
422
356
  ["s", value.to_str]
423
357
  elsif value.respond_to? :to_int
424
358
  i = value.to_int
425
- if -2_147_483_648 <= i && i < 2_147_483_648
359
+ if (-2_147_483_648...2_147_483_648).cover?(i)
426
360
  ["i", i]
427
361
  else
428
362
  ["x", i]
429
363
  end
430
364
  end
431
365
  end
432
- end # class PacketMarshaller
433
- end # module DBus
366
+ end
367
+ end