ruby-dbus 0.18.0.beta1 → 0.18.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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