ruby-dbus 0.16.0 → 0.18.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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