class DBus::PacketMarshaller
D-Bus packet marshaller class
Class that handles the conversion (marshalling) of Ruby objects to (binary) payload data.
Attributes
@return [:little,:big]
The current or result packet. FIXME: allow access only when marshalling is finished @return [String]
Public Class Methods
Make a [signature, value] pair for a variant
# File lib/dbus/marshall.rb 346 def self.make_variant(value) 347 # TODO: mix in _make_variant to String, Integer... 348 if value == true 349 ["b", true] 350 elsif value == false 351 ["b", false] 352 elsif value.nil? 353 ["b", nil] 354 elsif value.is_a? Float 355 ["d", value] 356 elsif value.is_a? Symbol 357 ["s", value.to_s] 358 elsif value.is_a? Array 359 ["av", value.map { |i| make_variant(i) }] 360 elsif value.is_a? Hash 361 h = {} 362 value.each_key { |k| h[k] = make_variant(value[k]) } 363 key_type = if value.empty? 364 "s" 365 else 366 t, = make_variant(value.first.first) 367 t 368 end 369 ["a{#{key_type}v}", h] 370 elsif value.respond_to? :to_str 371 ["s", value.to_str] 372 elsif value.respond_to? :to_int 373 i = value.to_int 374 if Data::Int32.range.cover?(i) 375 ["i", i] 376 elsif Data::Int64.range.cover?(i) 377 ["x", i] 378 else 379 ["t", i] 380 end 381 end 382 end
Create a new marshaller, setting the current packet to the empty packet.
# File lib/dbus/marshall.rb 172 def initialize(offset = 0, endianness: HOST_ENDIANNESS) 173 @endianness = endianness 174 @packet = "" 175 @offset = offset # for correct alignment of nested marshallers 176 end
Public Instance Methods
Align the buffer with NULL (0) bytes on a byte length of alignment.
# File lib/dbus/marshall.rb 190 def align(alignment) 191 pad_count = num_align(@offset + @packet.bytesize, alignment) - @offset 192 @packet = @packet.ljust(pad_count, 0.chr) 193 end
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 224 def append(type, val) 225 raise TypeException, "Cannot send nil" if val.nil? 226 227 type = type.chr if type.is_a?(Integer) 228 type = Type::Parser.new(type).parse[0] if type.is_a?(String) 229 # type is [Type] now 230 data_class = Data::BY_TYPE_CODE[type.sigtype] 231 if data_class.nil? 232 raise NotImplementedError, 233 "sigtype: #{type.sigtype} (#{type.sigtype.chr})" 234 end 235 236 if data_class.fixed? 237 align(data_class.alignment) 238 data = data_class.new(val) 239 @packet += data.marshall(endianness) 240 elsif data_class.basic? 241 val = val.value if val.is_a?(Data::Basic) 242 align(data_class.size_class.alignment) 243 size_data = data_class.size_class.new(val.bytesize) 244 @packet += size_data.marshall(endianness) 245 # Z* makes a binary string, as opposed to interpolation 246 @packet += [val].pack("Z*") 247 else 248 case type.sigtype 249 250 when Type::VARIANT 251 append_variant(val) 252 when Type::ARRAY 253 val = val.exact_value if val.is_a?(Data::Array) 254 append_array(type.child, val) 255 when Type::STRUCT, Type::DICT_ENTRY 256 val = val.exact_value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry) 257 unless val.is_a?(Array) || val.is_a?(Struct) 258 type_name = Type::TYPE_MAPPING[type.sigtype].first 259 raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}" 260 end 261 262 if type.sigtype == Type::DICT_ENTRY && val.size != 2 263 raise TypeException, "DICT_ENTRY expects a pair" 264 end 265 266 if type.members.size != val.size 267 type_name = Type::TYPE_MAPPING[type.sigtype].first 268 raise TypeException, "#{type_name} has #{val.size} elements but type info for #{type.members.size}" 269 end 270 271 struct do 272 type.members.zip(val).each do |t, v| 273 append(t, v) 274 end 275 end 276 else 277 raise NotImplementedError, 278 "sigtype: #{type.sigtype} (#{type.sigtype.chr})" 279 end 280 end 281 end
@param child_type [Type]
# File lib/dbus/marshall.rb 323 def append_array(child_type, val) 324 if val.is_a?(Hash) 325 raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY 326 327 # Damn ruby rocks here 328 val = val.to_a 329 end 330 # If string is received and ay is expected, explode the string 331 if val.is_a?(String) && child_type.sigtype == Type::BYTE 332 val = val.bytes 333 end 334 if !val.is_a?(Enumerable) 335 raise TypeException, "Expected an Enumerable of #{child_type.inspect} but got a #{val.class}" 336 end 337 338 array(child_type) do 339 val.each do |elem| 340 append(child_type, elem) 341 end 342 end 343 end
# File lib/dbus/marshall.rb 283 def append_variant(val) 284 vartype = nil 285 if val.is_a?(DBus::Data::Variant) 286 vartype = val.member_type 287 vardata = val.exact_value 288 elsif val.is_a?(DBus::Data::Container) 289 vartype = val.type 290 vardata = val.exact_value 291 elsif val.is_a?(DBus::Data::Base) 292 vartype = val.type 293 vardata = val.value 294 elsif val.is_a?(Array) && val.size == 2 295 case val[0] 296 when Type 297 vartype, vardata = val 298 # Ambiguous but easy to use, because Type 299 # cannot construct "as" "a{sv}" easily 300 when String 301 begin 302 parsed = Type::Parser.new(val[0]).parse 303 vartype = parsed[0] if parsed.size == 1 304 vardata = val[1] 305 rescue Type::SignatureException 306 # no assignment 307 end 308 end 309 end 310 if vartype.nil? 311 vartype, vardata = PacketMarshaller.make_variant(val) 312 vartype = Type::Parser.new(vartype).parse[0] 313 end 314 315 append(Data::Signature.type, vartype.to_s) 316 align(vartype.alignment) 317 sub = PacketMarshaller.new(@offset + @packet.bytesize, endianness: endianness) 318 sub.append(vartype, vardata) 319 @packet += sub.packet 320 end
Append the array type type to the packet and allow for appending the child elements.
# File lib/dbus/marshall.rb 197 def array(type) 198 # Thanks to Peter Rullmann for this line 199 align(4) 200 sizeidx = @packet.bytesize 201 @packet += "ABCD" 202 align(type.alignment) 203 contentidx = @packet.bytesize 204 yield 205 sz = @packet.bytesize - contentidx 206 raise InvalidPacketException if sz > 67_108_864 207 208 sz_data = Data::UInt32.new(sz) 209 @packet[sizeidx...sizeidx + 4] = sz_data.marshall(endianness) 210 end
Round num up to the specified power of two, alignment
# File lib/dbus/marshall.rb 179 def num_align(num, alignment) 180 case alignment 181 when 1, 2, 4, 8 182 bits = alignment - 1 183 (num + bits) & ~bits 184 else 185 raise ArgumentError, "Unsupported alignment #{alignment}" 186 end 187 end
Align and allow for appending struct fields.
# File lib/dbus/marshall.rb 213 def struct 214 align(8) 215 yield 216 end