Class: DBus::PacketMarshaller

Inherits:
Object
  • Object
show all
Defined in:
lib/dbus/marshall.rb

Overview

D-Bus packet marshaller class

Class that handles the conversion (marshalling) of Ruby objects to (binary) payload data.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(offset = 0, endianness: HOST_ENDIANNESS) ⇒ PacketMarshaller

Create a new marshaller, setting the current packet to the empty packet.



172
173
174
175
176
# File 'lib/dbus/marshall.rb', line 172

def initialize(offset = 0, endianness: HOST_ENDIANNESS)
  @endianness = endianness
  @packet = ""
  @offset = offset # for correct alignment of nested marshallers
end

Instance Attribute Details

#endianness:little, :big (readonly)

Returns:

  • (:little, :big)


168
169
170
# File 'lib/dbus/marshall.rb', line 168

def endianness
  @endianness
end

#packetString (readonly)

The current or result packet. FIXME: allow access only when marshalling is finished

Returns:

  • (String)


165
166
167
# File 'lib/dbus/marshall.rb', line 165

def packet
  @packet
end

Class Method Details

.make_variant(value) ⇒ Object

Make a [signature, value] pair for a variant



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/dbus/marshall.rb', line 346

def self.make_variant(value)
  # TODO: mix in _make_variant to String, Integer...
  if value == true
    ["b", true]
  elsif value == false
    ["b", false]
  elsif value.nil?
    ["b", nil]
  elsif value.is_a? Float
    ["d", value]
  elsif value.is_a? Symbol
    ["s", value.to_s]
  elsif value.is_a? Array
    ["av", value.map { |i| make_variant(i) }]
  elsif value.is_a? Hash
    h = {}
    value.each_key { |k| h[k] = make_variant(value[k]) }
    key_type = if value.empty?
                 "s"
               else
                 t, = make_variant(value.first.first)
                 t
               end
    ["a{#{key_type}v}", h]
  elsif value.respond_to? :to_str
    ["s", value.to_str]
  elsif value.respond_to? :to_int
    i = value.to_int
    if Data::Int32.range.cover?(i)
      ["i", i]
    elsif Data::Int64.range.cover?(i)
      ["x", i]
    else
      ["t", i]
    end
  end
end

Instance Method Details

#align(alignment) ⇒ Object

Align the buffer with NULL (0) bytes on a byte length of alignment.



190
191
192
193
# File 'lib/dbus/marshall.rb', line 190

def align(alignment)
  pad_count = num_align(@offset + @packet.bytesize, alignment) - @offset
  @packet = @packet.ljust(pad_count, 0.chr)
end

#append(type, val) ⇒ Object

Append a value val to the packet based on its type.

Host native endianness is used, declared in Message#marshall

Parameters:

Raises:



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/dbus/marshall.rb', line 224

def append(type, val)
  raise TypeException, "Cannot send nil" if val.nil?

  type = type.chr if type.is_a?(Integer)
  type = Type::Parser.new(type).parse[0] if type.is_a?(String)
  # type is [Type] now
  data_class = Data::BY_TYPE_CODE[type.sigtype]
  if data_class.nil?
    raise NotImplementedError,
          "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
  end

  if data_class.fixed?
    align(data_class.alignment)
    data = data_class.new(val)
    @packet += data.marshall(endianness)
  elsif data_class.basic?
    val = val.value if val.is_a?(Data::Basic)
    align(data_class.size_class.alignment)
    size_data = data_class.size_class.new(val.bytesize)
    @packet += size_data.marshall(endianness)
    # Z* makes a binary string, as opposed to interpolation
    @packet += [val].pack("Z*")
  else
    case type.sigtype

    when Type::VARIANT
      append_variant(val)
    when Type::ARRAY
      val = val.exact_value if val.is_a?(Data::Array)
      append_array(type.child, val)
    when Type::STRUCT, Type::DICT_ENTRY
      val = val.exact_value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry)
      unless val.is_a?(Array) || val.is_a?(Struct)
        type_name = Type::TYPE_MAPPING[type.sigtype].first
        raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}"
      end

      if type.sigtype == Type::DICT_ENTRY && val.size != 2
        raise TypeException, "DICT_ENTRY expects a pair"
      end

      if type.members.size != val.size
        type_name = Type::TYPE_MAPPING[type.sigtype].first
        raise TypeException, "#{type_name} has #{val.size} elements but type info for #{type.members.size}"
      end

      struct do
        type.members.zip(val).each do |t, v|
          append(t, v)
        end
      end
    else
      raise NotImplementedError,
            "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
    end
  end
end

#append_array(child_type, val) ⇒ Object

Parameters:



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/dbus/marshall.rb', line 323

def append_array(child_type, val)
  if val.is_a?(Hash)
    raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY

    # Damn ruby rocks here
    val = val.to_a
  end
  # If string is received and ay is expected, explode the string
  if val.is_a?(String) && child_type.sigtype == Type::BYTE
    val = val.bytes
  end
  if !val.is_a?(Enumerable)
    raise TypeException, "Expected an Enumerable of #{child_type.inspect} but got a #{val.class}"
  end

  array(child_type) do
    val.each do |elem|
      append(child_type, elem)
    end
  end
end

#append_variant(val) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/dbus/marshall.rb', line 283

def append_variant(val)
  vartype = nil
  if val.is_a?(DBus::Data::Variant)
    vartype = val.member_type
    vardata = val.exact_value
  elsif val.is_a?(DBus::Data::Container)
    vartype = val.type
    vardata = val.exact_value
  elsif val.is_a?(DBus::Data::Base)
    vartype = val.type
    vardata = val.value
  elsif val.is_a?(Array) && val.size == 2
    case val[0]
    when Type
      vartype, vardata = val
    # Ambiguous but easy to use, because Type
    # cannot construct "as" "a{sv}" easily
    when String
      begin
        parsed = Type::Parser.new(val[0]).parse
        vartype = parsed[0] if parsed.size == 1
        vardata = val[1]
      rescue Type::SignatureException
        # no assignment
      end
    end
  end
  if vartype.nil?
    vartype, vardata = PacketMarshaller.make_variant(val)
    vartype = Type::Parser.new(vartype).parse[0]
  end

  append(Data::Signature.type, vartype.to_s)
  align(vartype.alignment)
  sub = PacketMarshaller.new(@offset + @packet.bytesize, endianness: endianness)
  sub.append(vartype, vardata)
  @packet += sub.packet
end

#array(type) ⇒ Object

Append the array type type to the packet and allow for appending the child elements.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/dbus/marshall.rb', line 197

def array(type)
  # Thanks to Peter Rullmann for this line
  align(4)
  sizeidx = @packet.bytesize
  @packet += "ABCD"
  align(type.alignment)
  contentidx = @packet.bytesize
  yield
  sz = @packet.bytesize - contentidx
  raise InvalidPacketException if sz > 67_108_864

  sz_data = Data::UInt32.new(sz)
  @packet[sizeidx...sizeidx + 4] = sz_data.marshall(endianness)
end

#num_align(num, alignment) ⇒ Object

Round num up to the specified power of two, alignment



179
180
181
182
183
184
185
186
187
# File 'lib/dbus/marshall.rb', line 179

def num_align(num, alignment)
  case alignment
  when 1, 2, 4, 8
    bits = alignment - 1
    num + bits & ~bits
  else
    raise ArgumentError, "Unsupported alignment #{alignment}"
  end
end

#structObject

Align and allow for appending struct fields.



213
214
215
216
# File 'lib/dbus/marshall.rb', line 213

def struct
  align(8)
  yield
end