class DBus::PacketMarshaller

D-Bus packet marshaller class

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

Attributes

endianness[R]

@return [:little,:big]

packet[R]

The current or result packet. FIXME: allow access only when marshalling is finished @return [String]

Public Class Methods

make_variant(value) click to toggle source

Make a [signature, value] pair for a variant

# 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
new(offset = 0, endianness: HOST_ENDIANNESS) click to toggle source

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

# 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

Public Instance Methods

align(alignment) click to toggle source

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

# 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) click to toggle source

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

Host native endianness is used, declared in Message#marshall

@param type [SingleCompleteType] (or Integer or {Type}) @param val [::Object]

# 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) click to toggle source

@param child_type [Type]

# 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 recieved 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) click to toggle source
# 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) { || ... } click to toggle source

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

# 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) click to toggle source

Round num up to the specified power of two, alignment

# 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
struct() { || ... } click to toggle source

Align and allow for appending struct fields.

# File lib/dbus/marshall.rb, line 213
def struct
  align(8)
  yield
end