ruby-dbus 0.16.0 → 0.18.1

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