ruby-dbus 0.18.0.beta1 → 0.18.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2022 Martin Vidner
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License, version 2.1 as published by the Free Software Foundation.
9
+ # See the file "COPYING" for the exact licensing terms.
10
+
11
+ module DBus
12
+ # A message while it is being parsed: a binary string,
13
+ # with a position cursor (*pos*), and an *endianness* tag.
14
+ class RawMessage
15
+ # @return [String]
16
+ # attr_reader :bytes
17
+
18
+ # @return [Integer] position in the byte buffer
19
+ attr_reader :pos
20
+
21
+ # @return [:little,:big]
22
+ attr_reader :endianness
23
+
24
+ # @param bytes [String]
25
+ # @param endianness [:little,:big,nil]
26
+ # if not given, read the 1st byte of *bytes*
27
+ def initialize(bytes, endianness = nil)
28
+ @bytes = bytes
29
+ @pos = 0
30
+ @endianness = endianness || self.class.endianness(@bytes[0])
31
+ end
32
+
33
+ # Get the endiannes switch as a Symbol,
34
+ # which will make using it slightly more efficient
35
+ # @param tag_char [String]
36
+ # @return [:little,:big]
37
+ def self.endianness(tag_char)
38
+ case tag_char
39
+ when LIL_END
40
+ :little
41
+ when BIG_END
42
+ :big
43
+ else
44
+ raise InvalidPacketException, "Incorrect endianness #{tag_char.inspect}"
45
+ end
46
+ end
47
+
48
+ # @return [void]
49
+ # @raise IncompleteBufferException if there are not enough bytes remaining
50
+ def want!(size)
51
+ raise IncompleteBufferException if @pos + size > @bytes.bytesize
52
+ end
53
+
54
+ # @return [String]
55
+ # @raise IncompleteBufferException if there are not enough bytes remaining
56
+ # TODO: stress test this with encodings. always binary?
57
+ def read(size)
58
+ want!(size)
59
+ ret = @bytes.slice(@pos, size)
60
+ @pos += size
61
+ ret
62
+ end
63
+
64
+ # @return [String]
65
+ # @api private
66
+ def remaining_bytes
67
+ # This returns "" if pos is just past the end of the string,
68
+ # and nil if it is further.
69
+ @bytes[@pos..-1]
70
+ end
71
+
72
+ # Align the *pos* index on a multiple of *alignment*
73
+ # @param alignment [Integer] must be 1, 2, 4 or 8
74
+ # @return [void]
75
+ def align(alignment)
76
+ case alignment
77
+ when 1
78
+ nil
79
+ when 2, 4, 8
80
+ bits = alignment - 1
81
+ pad_size = ((@pos + bits) & ~bits) - @pos
82
+ pad = read(pad_size)
83
+ unless pad.bytes.all?(&:zero?)
84
+ raise InvalidPacketException, "Alignment bytes are not NUL"
85
+ end
86
+ else
87
+ raise ArgumentError, "Unsupported alignment #{alignment}"
88
+ end
89
+ end
90
+ end
91
+ end
data/lib/dbus/type.rb CHANGED
@@ -33,7 +33,11 @@ module DBus
33
33
  #
34
34
  # This module containts the constants of the types specified in the D-Bus
35
35
  # protocol.
36
- module Type
36
+ #
37
+ # Corresponds to {SingleCompleteType}.
38
+ #
39
+ # See also {DBus::Data::Signature}
40
+ class Type
37
41
  # Mapping from type number to name and alignment.
38
42
  TYPE_MAPPING = {
39
43
  0 => ["INVALID", nil],
@@ -64,91 +68,117 @@ module DBus
64
68
  class SignatureException < Exception
65
69
  end
66
70
 
67
- # = D-Bus type conversion class
68
- #
69
- # Helper class for representing a D-Bus type.
70
- class Type
71
- # Returns the signature type number.
72
- attr_reader :sigtype
73
- # Return contained member types.
74
- attr_reader :members
75
-
76
- # Create a new type instance for type number _sigtype_.
77
- def initialize(sigtype)
78
- if !TYPE_MAPPING.keys.member?(sigtype)
79
- raise SignatureException, "Unknown key in signature: #{sigtype.chr}"
80
- end
71
+ # Formerly this was a Module and there was a DBus::Type::Type class
72
+ # but the class got too prominent to keep its double double name.
73
+ # This is for backward compatibility.
74
+ Type = self # rubocop:disable Naming/ConstantName
81
75
 
82
- @sigtype = sigtype
83
- @members = []
84
- end
76
+ # @return [String] the signature type character, eg "s" or "e".
77
+ attr_reader :sigtype
78
+ # @return [Array<Type>] contained member types.
79
+ attr_reader :members
85
80
 
86
- # Return the required alignment for the type.
87
- def alignment
88
- TYPE_MAPPING[@sigtype].last
81
+ # Use {DBus.type} instead, because this allows constructing
82
+ # incomplete or invalid types, for backward compatibility.
83
+ #
84
+ # @param abstract [Boolean] allow abstract types "r" and "e"
85
+ # (Enabled for internal usage by {Parser}.)
86
+ def initialize(sigtype, abstract: false)
87
+ if !TYPE_MAPPING.keys.member?(sigtype)
88
+ case sigtype
89
+ when ")"
90
+ raise SignatureException, "STRUCT unexpectedly closed: )"
91
+ when "}"
92
+ raise SignatureException, "DICT_ENTRY unexpectedly closed: }"
93
+ else
94
+ raise SignatureException, "Unknown type code #{sigtype.inspect}"
95
+ end
89
96
  end
90
97
 
91
- # Return a string representation of the type according to the
92
- # D-Bus specification.
93
- def to_s
94
- case @sigtype
98
+ unless abstract
99
+ case sigtype
95
100
  when STRUCT
96
- "(#{@members.collect(&:to_s).join})"
97
- when ARRAY
98
- "a#{child}"
101
+ raise SignatureException, "Abstract STRUCT, use \"(...)\" instead of \"#{STRUCT}\""
99
102
  when DICT_ENTRY
100
- "{#{@members.collect(&:to_s).join}}"
101
- else
102
- if !TYPE_MAPPING.keys.member?(@sigtype)
103
- raise NotImplementedError
104
- end
105
-
106
- @sigtype.chr
103
+ raise SignatureException, "Abstract DICT_ENTRY, use \"{..}\" instead of \"#{DICT_ENTRY}\""
107
104
  end
108
105
  end
109
106
 
110
- # Add a new member type _item_.
111
- def <<(item)
112
- if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
113
- raise SignatureException
114
- end
115
- raise SignatureException if @sigtype == ARRAY && !@members.empty?
116
-
117
- if @sigtype == DICT_ENTRY
118
- case @members.size
119
- when 2
120
- raise SignatureException, "Dict entries have exactly two members"
121
- when 0
122
- if [STRUCT, ARRAY, DICT_ENTRY].member?(item.sigtype)
123
- raise SignatureException, "Dict entry keys must be basic types"
124
- end
125
- end
107
+ @sigtype = sigtype
108
+ @members = []
109
+ end
110
+
111
+ # Return the required alignment for the type.
112
+ def alignment
113
+ TYPE_MAPPING[@sigtype].last
114
+ end
115
+
116
+ # Return a string representation of the type according to the
117
+ # D-Bus specification.
118
+ def to_s
119
+ case @sigtype
120
+ when STRUCT
121
+ "(#{@members.collect(&:to_s).join})"
122
+ when ARRAY
123
+ "a#{child}"
124
+ when DICT_ENTRY
125
+ "{#{@members.collect(&:to_s).join}}"
126
+ else
127
+ if !TYPE_MAPPING.keys.member?(@sigtype)
128
+ raise NotImplementedError
126
129
  end
127
- @members << item
130
+
131
+ @sigtype.chr
128
132
  end
133
+ end
129
134
 
130
- # Return the first contained member type.
131
- def child
132
- @members[0]
135
+ # Add a new member type _item_.
136
+ def <<(item)
137
+ if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
138
+ raise SignatureException
133
139
  end
140
+ raise SignatureException if @sigtype == ARRAY && !@members.empty?
134
141
 
135
- def inspect
136
- s = TYPE_MAPPING[@sigtype].first
137
- if [STRUCT, ARRAY].member?(@sigtype)
138
- s += ": #{@members.inspect}"
142
+ if @sigtype == DICT_ENTRY
143
+ case @members.size
144
+ when 2
145
+ raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more in #{@signature}"
146
+ when 0
147
+ if [STRUCT, ARRAY, DICT_ENTRY, VARIANT].member?(item.sigtype)
148
+ raise SignatureException, "DICT_ENTRY key must be basic (non-container)"
149
+ end
139
150
  end
140
- s
141
151
  end
152
+ @members << item
153
+ end
154
+
155
+ # Return the first contained member type.
156
+ def child
157
+ @members[0]
158
+ end
159
+
160
+ def inspect
161
+ s = TYPE_MAPPING[@sigtype].first
162
+ if [STRUCT, ARRAY].member?(@sigtype)
163
+ s += ": #{@members.inspect}"
164
+ end
165
+ s
142
166
  end
143
167
 
144
168
  # = D-Bus type parser class
145
169
  #
146
170
  # Helper class to parse a type signature in the protocol.
171
+ # @api private
147
172
  class Parser
148
173
  # Create a new parser for the given _signature_.
149
174
  # @param signature [Signature]
150
175
  def initialize(signature)
151
176
  @signature = signature
177
+ if signature.size > 255
178
+ msg = "Potential signature is longer than 255 characters (#{@signature.size}): #{@signature}"
179
+ raise SignatureException, msg
180
+ end
181
+
152
182
  @idx = 0
153
183
  end
154
184
 
@@ -160,28 +190,45 @@ module DBus
160
190
  end
161
191
 
162
192
  # Parse one character _char_ of the signature.
163
- def parse_one(char)
193
+ # @param for_array [Boolean] are we parsing an immediate child of an ARRAY
194
+ # @return [Type]
195
+ def parse_one(char, for_array: false)
164
196
  res = nil
165
197
  case char
166
198
  when "a"
167
199
  res = Type.new(ARRAY)
168
200
  char = nextchar
169
- raise SignatureException, "Parse error in #{@signature}" if char.nil?
201
+ raise SignatureException, "Empty ARRAY in #{@signature}" if char.nil?
170
202
 
171
- child = parse_one(char)
203
+ child = parse_one(char, for_array: true)
172
204
  res << child
173
205
  when "("
174
- res = Type.new(STRUCT)
206
+ res = Type.new(STRUCT, abstract: true)
175
207
  while (char = nextchar) && char != ")"
176
208
  res << parse_one(char)
177
209
  end
178
- raise SignatureException, "Parse error in #{@signature}" if char.nil?
210
+ raise SignatureException, "STRUCT not closed in #{@signature}" if char.nil?
211
+ raise SignatureException, "Empty STRUCT in #{@signature}" if res.members.empty?
179
212
  when "{"
180
- res = Type.new(DICT_ENTRY)
181
- while (char = nextchar) && char != "}"
213
+ raise SignatureException, "DICT_ENTRY not an immediate child of an ARRAY" unless for_array
214
+
215
+ res = Type.new(DICT_ENTRY, abstract: true)
216
+
217
+ # key type, value type
218
+ 2.times do |i|
219
+ char = nextchar
220
+ raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil?
221
+
222
+ raise SignatureException, "DICT_ENTRY must have 2 subtypes, found #{i} in #{@signature}" if char == "}"
223
+
182
224
  res << parse_one(char)
183
225
  end
184
- raise SignatureException, "Parse error in #{@signature}" if char.nil?
226
+
227
+ # closing "}"
228
+ char = nextchar
229
+ raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil?
230
+
231
+ raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more in #{@signature}" if char != "}"
185
232
  else
186
233
  res = Type.new(char)
187
234
  end
@@ -189,6 +236,7 @@ module DBus
189
236
  end
190
237
 
191
238
  # Parse the entire signature, return a DBus::Type object.
239
+ # @return [Array<Type>]
192
240
  def parse
193
241
  @idx = 0
194
242
  ret = []
@@ -197,18 +245,47 @@ module DBus
197
245
  end
198
246
  ret
199
247
  end
248
+
249
+ # Parse one {SingleCompleteType}
250
+ # @return [Type]
251
+ def parse1
252
+ c = nextchar
253
+ raise SignatureException, "Empty signature, expecting a Single Complete Type" if c.nil?
254
+
255
+ t = parse_one(c)
256
+ raise SignatureException, "Has more than a Single Complete Type: #{@signature}" unless nextchar.nil?
257
+
258
+ t
259
+ end
200
260
  end
201
261
  end
202
262
 
203
263
  # shortcuts
204
264
 
205
- # Parse a String to a DBus::Type::Type
265
+ # Parse a String to a valid {DBus::Type}.
266
+ # This is prefered to {Type#initialize} which allows
267
+ # incomplete or invalid types.
268
+ # @param string_type [SingleCompleteType]
269
+ # @return [DBus::Type]
270
+ # @raise SignatureException
206
271
  def type(string_type)
207
- Type::Parser.new(string_type).parse[0]
272
+ Type::Parser.new(string_type).parse1
208
273
  end
209
274
  module_function :type
210
275
 
276
+ # Parse a String to zero or more {DBus::Type}s.
277
+ # @param string_type [Signature]
278
+ # @return [Array<DBus::Type>]
279
+ # @raise SignatureException
280
+ def types(string_type)
281
+ Type::Parser.new(string_type).parse
282
+ end
283
+ module_function :types
284
+
211
285
  # Make an explicit [Type, value] pair
286
+ # @param string_type [SingleCompleteType]
287
+ # @param value [::Object]
288
+ # @return [Array(DBus::Type::Type,::Object)]
212
289
  def variant(string_type, value)
213
290
  [type(string_type), value]
214
291
  end
data/lib/dbus.rb CHANGED
@@ -14,6 +14,7 @@ require_relative "dbus/api_options"
14
14
  require_relative "dbus/auth"
15
15
  require_relative "dbus/bus"
16
16
  require_relative "dbus/bus_name"
17
+ require_relative "dbus/data"
17
18
  require_relative "dbus/error"
18
19
  require_relative "dbus/introspect"
19
20
  require_relative "dbus/logger"
@@ -26,6 +27,7 @@ require_relative "dbus/object_path"
26
27
  require_relative "dbus/proxy_object"
27
28
  require_relative "dbus/proxy_object_factory"
28
29
  require_relative "dbus/proxy_object_interface"
30
+ require_relative "dbus/raw_message"
29
31
  require_relative "dbus/type"
30
32
  require_relative "dbus/xml"
31
33
 
@@ -49,6 +51,10 @@ module DBus
49
51
  BIG_END
50
52
  end
51
53
 
54
+ # Comparing symbols is faster than strings
55
+ # @return [:little,:big]
56
+ HOST_ENDIANNESS = RawMessage.endianness(HOST_END)
57
+
52
58
  # General exceptions.
53
59
 
54
60
  # Exception raised when there is a problem with a type (may be unknown or