# dbus.rb - Module containing the low-level D-Bus implementation # # This file is part of the ruby-dbus project # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License, version 2.1 as published by the Free Software Foundation. # See the file "COPYING" for the exact licensing terms. # = D-Bus main module # # Module containing all the D-Bus modules and classes. module DBus # = InvalidDestinationName class # Thrown when you try to send a message to /org/freedesktop/DBus/Local, that # is reserved. class InvalidDestinationName < Exception end # = D-Bus message class # # Class that holds any type of message that travels over the bus. class Message # The serial number of the message. @@serial = 1 # Mutex that protects updates on the serial number. @@serial_mutex = Mutex.new # Type of a message (by specification). MESSAGE_SIGNATURE = "yyyyuua(yv)" # FIXME: following message type constants should be under Message::Type IMO # well, yeah sure # # Invalid message type. INVALID = 0 # Method call message type. METHOD_CALL = 1 # Method call return value message type. METHOD_RETURN = 2 # Error message type. ERROR = 3 # Signal message type. SIGNAL = 4 # Message flag signyfing that no reply is expected. NO_REPLY_EXPECTED = 0x1 # Message flag signifying that no automatic start is required/must be # performed. NO_AUTO_START = 0x2 # The type of the message. attr_reader :message_type # The path of the object instance the message must be sent to/is sent from. attr_accessor :path # The interface of the object that must be used/was used. attr_accessor :interface # The interface member (method/signal name) of the object that must be # used/was used. attr_accessor :member # The name of the error (in case of an error message type). attr_accessor :error_name # The destination connection of the object that must be used/was used. attr_accessor :destination # The sender of the message. attr_accessor :sender # The signature of the message contents. attr_accessor :signature # The serial number of the message this message is a reply for. attr_accessor :reply_serial # The protocol. attr_reader :protocol # The serial of the message. attr_reader :serial # The parameters of the message. attr_reader :params # Create a message with message type _mtype_ with default values and a # unique serial number. def initialize(mtype = INVALID) @message_type = mtype @flags = 0 @protocol = 1 @body_length = 0 @signature = String.new @@serial_mutex.synchronize do @serial = @@serial @@serial += 1 end @params = Array.new @destination = nil @interface = nil @error_name = nil @member = nil @path = nil @reply_serial = nil if mtype == METHOD_RETURN @flags = NO_REPLY_EXPECTED end end def to_s "#{message_type} sender=#{sender} -> dest=#{destination} serial=#{serial} reply_serial=#{reply_serial} path=#{path}; interface=#{interface}; member=#{member} error_name=#{error_name}" end # Create a regular reply to a message _m_. def self.method_return(m) MethodReturnMessage.new.reply_to(m) end # Create an error reply to a message _m_. def self.error(m, error_name, description=nil) ErrorMessage.new(error_name, description).reply_to(m) end # Mark this message as a reply to a another message _m_, taking # the serial number of _m_ as reply serial and the sender of _m_ as # destination. def reply_to(m) @reply_serial = m.serial @destination = m.sender self end # Add a parameter _val_ of type _type_ to the message. def add_param(type, val) type = type.chr if type.kind_of?(Fixnum) @signature += type.to_s @params << [type, val] end # FIXME: what are these? a message element constant enumeration? # See method below, in a message, you have and array of optional parameters # that come with an index, to determine their meaning. The values are in # spec, more a definition than an enumeration. PATH = 1 INTERFACE = 2 MEMBER = 3 ERROR_NAME = 4 REPLY_SERIAL = 5 DESTINATION = 6 SENDER = 7 SIGNATURE = 8 # Marshall the message with its current set parameters and return # it in a packet form. def marshall if @path == "/org/freedesktop/DBus/Local" raise InvalidDestinationName end params = PacketMarshaller.new @params.each do |param| params.append(param[0], param[1]) end @body_length = params.packet.bytesize marshaller = PacketMarshaller.new marshaller.append(Type::BYTE, HOST_END) marshaller.append(Type::BYTE, @message_type) marshaller.append(Type::BYTE, @flags) marshaller.append(Type::BYTE, @protocol) marshaller.append(Type::UINT32, @body_length) marshaller.append(Type::UINT32, @serial) headers = [] headers << [PATH, ["o", @path]] if @path headers << [INTERFACE, ["s", @interface]] if @interface headers << [MEMBER, ["s", @member]] if @member headers << [ERROR_NAME, ["s", @error_name]] if @error_name headers << [REPLY_SERIAL, ["u", @reply_serial]] if @reply_serial headers << [DESTINATION, ["s", @destination]] if @destination # SENDER is not sent, the message bus fills it in instead headers << [SIGNATURE, ["g", @signature]] if @signature != "" marshaller.append("a(yv)", headers) marshaller.align(8) @params.each do |param| marshaller.append(param[0], param[1]) end marshaller.packet end # Unmarshall a packet contained in the buffer _buf_ and set the # parameters of the message object according the data found in the # buffer. # Return the detected message and the index pointer of the buffer where # the message data ended. def unmarshall_buffer(buf) buf = buf.dup if buf[0] == ?l endianness = LIL_END else endianness = BIG_END end pu = PacketUnmarshaller.new(buf, endianness) mdata = pu.unmarshall(MESSAGE_SIGNATURE) _, @message_type, @flags, @protocol, @body_length, @serial, headers = mdata headers.each do |struct| case struct[0] when PATH @path = struct[1] when INTERFACE @interface = struct[1] when MEMBER @member = struct[1] when ERROR_NAME @error_name = struct[1] when REPLY_SERIAL @reply_serial = struct[1] when DESTINATION @destination = struct[1] when SENDER @sender = struct[1] when SIGNATURE @signature = struct[1] end end pu.align(8) if @body_length > 0 and @signature @params = pu.unmarshall(@signature, @body_length) end [self, pu.idx] end # def unmarshall_buf # Unmarshall the data of a message found in the buffer _buf_ using # Message#unmarshall_buf. # Return the message. def unmarshall(buf) ret, _ = unmarshall_buffer(buf) ret end end # class Message class MethodReturnMessage < Message def initialize super(METHOD_RETURN) end end class ErrorMessage < Message def initialize(error_name, description=nil) super(ERROR) @error_name = error_name unless description.nil? add_param(Type::STRING, description) end end def self.from_exception(ex) name = if ex.is_a? DBus::Error ex.name else "org.freedesktop.DBus.Error.Failed" # ex.class.to_s # RuntimeError is not a valid name, has no dot end description = ex.message msg = self.new(name, description) msg.add_param(DBus.type("as"), ex.backtrace) msg end end end # module DBus