1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 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
|
# 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)".freeze
# 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 = ""
@@serial_mutex.synchronize do
@serial = @@serial
@@serial += 1
end
@params = []
@destination = nil
@interface = nil
@error_name = nil
@member = nil
@path = nil
@reply_serial = nil
@flags = NO_REPLY_EXPECTED if mtype == METHOD_RETURN
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.is_a?(Integer)
@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 [Array(Message,Integer)]
# the detected message (self) and
# the index pointer of the buffer where the message data ended.
def unmarshall_buffer(buf)
buf = buf.dup
endianness = if buf[0] == "l"
LIL_END
else
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 && @signature
@params = pu.unmarshall(@signature, @body_length)
end
[self, pu.idx]
end
# Make a new exception from ex, mark it as being caused by this message
# @api private
def annotate_exception(ex)
new_ex = ex.exception("#{ex}; caused by #{self}")
new_ex.set_backtrace(ex.backtrace)
new_ex
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
add_param(Type::STRING, description) unless description.nil?
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 = new(name, description)
msg.add_param(DBus.type("as"), ex.backtrace)
msg
end
end
end # module DBus
|