class DBus::PacketUnmarshaller

D-Bus packet unmarshaller class

Class that handles the conversion (unmarshalling) of payload data to #{::Object}s (in *plain* mode) or to {Data::Base} (in *exact* mode)

Spelling note: this codebase always uses a double L in the “marshall” word and its inflections.

Public Class Methods

new(buffer, endianness) click to toggle source

Create a new unmarshaller for the given data buffer. @param buffer [String] @param endianness [:little,:big]

# File lib/dbus/marshall.rb, line 35
def initialize(buffer, endianness)
  # TODO: this dup can be avoided if we can prove
  # that an IncompleteBufferException leaves the original *buffer* intact
  buffer = buffer.dup
  @raw_msg = RawMessage.new(buffer, endianness)
end

Public Instance Methods

align_body() click to toggle source

after the headers, the body starts 8-aligned

# File lib/dbus/marshall.rb, line 65
def align_body
  @raw_msg.align(8)
end
consumed_size() click to toggle source

@return [Integer]

# File lib/dbus/marshall.rb, line 70
def consumed_size
  @raw_msg.pos
end
unmarshall(signature, len = nil, mode: :plain) click to toggle source

Unmarshall the buffer for a given signature and length len. Return an array of unmarshalled objects. @param signature [Signature] @param len [Integer,nil] if given, and there is not enough data

in the buffer, raise {IncompleteBufferException}

@param mode [:plain,:exact] @return [Array<::Object,DBus::Data::Base>]

Objects in `:plain` mode, {DBus::Data::Base} in `:exact` mode
The array size corresponds to the number of types in *signature*.

@raise IncompleteBufferException @raise InvalidPacketException

# File lib/dbus/marshall.rb, line 53
def unmarshall(signature, len = nil, mode: :plain)
  @raw_msg.want!(len) if len

  sigtree = Type::Parser.new(signature).parse
  ret = []
  sigtree.each do |elem|
    ret << do_parse(elem, mode: mode)
  end
  ret
end

Private Instance Methods

aligned_read_value(data_class) click to toggle source

@param data_class [Class] a subclass of Data::Base (specific?) @return [::Integer,::Float]

# File lib/dbus/marshall.rb, line 78
def aligned_read_value(data_class)
  @raw_msg.align(data_class.alignment)
  bytes = @raw_msg.read(data_class.alignment)
  bytes.unpack1(data_class.format[@raw_msg.endianness])
end
do_parse(signature, mode: :plain) click to toggle source

Based on the signature type, retrieve a packet from the buffer and return it. @param signature [Type] @param mode [:plain,:exact] @return [Data::Base]

# File lib/dbus/marshall.rb, line 89
def do_parse(signature, mode: :plain)
  # FIXME: better naming for packet vs value
  packet = nil
  data_class = Data::BY_TYPE_CODE[signature.sigtype]

  if data_class.nil?
    raise NotImplementedError,
          "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})"
  end

  if data_class.fixed?
    value = aligned_read_value(data_class)
    packet = data_class.from_raw(value, mode: mode)
  elsif data_class.basic?
    size = aligned_read_value(data_class.size_class)
    # @raw_msg.align(data_class.alignment)
    # ^ is not necessary because we've just read a suitably-aligned *size*
    value = @raw_msg.read(size)
    nul = @raw_msg.read(1)
    if nul != "\u0000"
      raise InvalidPacketException, "#{data_class} is not NUL-terminated"
    end

    packet = data_class.from_raw(value, mode: mode)
  else
    @raw_msg.align(data_class.alignment)
    case signature.sigtype
    when Type::STRUCT, Type::DICT_ENTRY
      values = signature.members.map do |child_sig|
        do_parse(child_sig, mode: mode)
      end
      packet = data_class.from_items(values, mode: mode, type: signature)

    when Type::VARIANT
      data_sig = do_parse(Data::Signature.type, mode: :exact) # -> Data::Signature
      types = Type::Parser.new(data_sig.value).parse # -> Array<Type>
      unless types.size == 1
        raise InvalidPacketException, "VARIANT must contain 1 value, #{types.size} found"
      end

      type = types.first
      value = do_parse(type, mode: mode)
      packet = data_class.from_items(value, mode: mode, member_type: type)

    when Type::ARRAY
      array_bytes = aligned_read_value(Data::UInt32)
      if array_bytes > 67_108_864
        raise InvalidPacketException, "ARRAY body longer than 64MiB"
      end

      # needed here because of empty arrays
      @raw_msg.align(signature.child.alignment)

      items = []
      end_pos = @raw_msg.pos + array_bytes
      while @raw_msg.pos < end_pos
        item = do_parse(signature.child, mode: mode)
        items << item
      end
      is_hash = signature.child.sigtype == Type::DICT_ENTRY
      packet = data_class.from_items(items, mode: mode, type: signature, hash: is_hash)
    end
  end
  packet
end