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) ⇒ PacketMarshaller

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



240
241
242
243
# File 'lib/dbus/marshall.rb', line 240

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

Instance Attribute Details

#packetObject (readonly)

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



236
237
238
# File 'lib/dbus/marshall.rb', line 236

def packet
  @packet
end

Class Method Details

.make_variant(value) ⇒ Object

Make a [signature, value] pair for a variant



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/dbus/marshall.rb', line 402

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]) }
    ["a{sv}", h]
  elsif value.respond_to? :to_str
    ["s", value.to_str]
  elsif value.respond_to? :to_int
    i = value.to_int
    if -2_147_483_648 <= i && i < 2_147_483_648
      ["i", i]
    else
      ["x", i]
    end
  end
end

Instance Method Details

#align(a) ⇒ Object

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



257
258
259
# File 'lib/dbus/marshall.rb', line 257

def align(a)
  @packet = @packet.ljust(num_align(@offset + @packet.bytesize, a) - @offset, 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

Raises:



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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
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
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/dbus/marshall.rb', line 296

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)
  case type.sigtype
  when Type::BYTE
    @packet += val.chr
  when Type::UINT32, Type::UNIX_FD
    align(4)
    @packet += [val].pack("L")
  when Type::UINT64
    align(8)
    @packet += [val].pack("Q")
  when Type::INT64
    align(8)
    @packet += [val].pack("q")
  when Type::INT32
    align(4)
    @packet += [val].pack("l")
  when Type::UINT16
    align(2)
    @packet += [val].pack("S")
  when Type::INT16
    align(2)
    @packet += [val].pack("s")
  when Type::DOUBLE
    align(8)
    @packet += [val].pack("d")
  when Type::BOOLEAN
    align(4)
    @packet += if val
                 [1].pack("L")
               else
                 [0].pack("L")
               end
  when Type::OBJECT_PATH
    append_string(val)
  when Type::STRING
    append_string(val)
  when Type::SIGNATURE
    append_signature(val)
  when Type::VARIANT
    vartype = nil
    if val.is_a?(Array) && val.size == 2
      if val[0].is_a?(DBus::Type::Type)
        vartype, vardata = val
      elsif val[0].is_a?(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_signature(vartype.to_s)
    align(vartype.alignment)
    sub = PacketMarshaller.new(@offset + @packet.bytesize)
    sub.append(vartype, vardata)
    @packet += sub.packet
  when Type::ARRAY
    if val.is_a?(Hash)
      raise TypeException, "Expected an Array but got a Hash" if type.child.sigtype != Type::DICT_ENTRY
      # Damn ruby rocks here
      val = val.to_a
    end
    # If string is recieved and ay is expected, explode the string
    if val.is_a?(String) && type.child.sigtype == Type::BYTE
      val = val.bytes
    end
    if !val.is_a?(Enumerable)
      raise TypeException, "Expected an Enumerable of #{type.child.inspect} but got a #{val.class}"
    end
    array(type.child) do
      val.each do |elem|
        append(type.child, elem)
      end
    end
  when Type::STRUCT, Type::DICT_ENTRY
    # TODO: use duck typing, val.respond_to?
    raise TypeException, "Struct/DE expects an Array" if !val.is_a?(Array)
    if type.sigtype == Type::DICT_ENTRY && val.size != 2
      raise TypeException, "Dict entry expects a pair"
    end
    if type.members.size != val.size
      raise TypeException, "Struct/DE 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

#append_signature(str) ⇒ Object

Append the the signature signature itself to the packet.



268
269
270
# File 'lib/dbus/marshall.rb', line 268

def append_signature(str)
  @packet += str.bytesize.chr + str + "\0"
end

#append_string(str) ⇒ Object

Append the the string str itself to the packet.



262
263
264
265
# File 'lib/dbus/marshall.rb', line 262

def append_string(str)
  align(4)
  @packet += [str.bytesize].pack("L") + [str].pack("Z*")
end

#array(type) ⇒ Object

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



274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/dbus/marshall.rb', line 274

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
  @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
end

#num_align(n, a) ⇒ Object

Round n up to the specified power of two, a



246
247
248
249
250
251
252
253
254
# File 'lib/dbus/marshall.rb', line 246

def num_align(n, a)
  case a
  when 1, 2, 4, 8
    bits = a - 1
    n + bits & ~bits
  else
    raise "Unsupported alignment"
  end
end

#structObject

Align and allow for appending struct fields.



288
289
290
291
# File 'lib/dbus/marshall.rb', line 288

def struct
  align(8)
  yield
end