ruby-dbus 0.19.0 → 0.21.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7261c5ad0fb4e067da97b31298e6a79023e54743f69563b22c9a751101df2f85
4
- data.tar.gz: 38075d862b0407889bf0a0e25eff450d18c8e225012a028545700fc1be15c342
3
+ metadata.gz: 922468208faa7a9a13f3c40c0b7ffd22a5a9a6b00de2e30e05e6ae18c84662d5
4
+ data.tar.gz: 9386581c654bad7f970763c1c67f8984e95654d6f49673a1c3452c2cc59ced0f
5
5
  SHA512:
6
- metadata.gz: 700dc07af8d28f7a7537be292dd6313268032f169102c3581d2f5c7a13ba8b9b7c864b23d3b8bdb66e3760a147a7edf7d835098f450625ffade7f748e101f987
7
- data.tar.gz: 2637cb7cad666a415c6e4a83a158b116507e5d6db3e70a53f5384d1a9240f191e6e2b540950076ae76397b7581c891e71b9f59e84346647394fe0bd7833e49c1
6
+ metadata.gz: 67e6e4058de8f6e9e0eddc404f53b5219487d711c165dd607ec1138c18f089de560f6467bf6960a28dc0c29b6df2a4a5eee4156e2c2fab68c1dada5ce9383f91
7
+ data.tar.gz: 364d914924b85f11354fdf8ed3f44f446091422014a92c5c7f0ecc3175cc28236d6a6b0cfec233321657a35554eb24be8692ab83c41a65eb9a75a7eef5dd7a5b
data/NEWS.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Ruby D-Bus 0.21.0 - 2023-04-08
6
+
7
+ Features:
8
+ * Respect env RUBY_DBUS_ENDIANNESS=B (or =l) for outgoing messages.
9
+
10
+ Bug fixes:
11
+ * Reduce socket buffer allocations ([#129][]).
12
+ * Message#marshall speedup: don't marshall the body twice.
13
+
14
+ [#129]: https://github.com/mvidner/ruby-dbus/pull/129
15
+
16
+ ## Ruby D-Bus 0.20.0 - 2023-03-21
17
+
18
+ Features:
19
+ * For EXTERNAL authentication, try also without the user id, to work with
20
+ containers ([#126][]).
21
+ * Thread safety, as long as the non-main threads only send signals.
22
+
23
+ [#126]: https://github.com/mvidner/ruby-dbus/issues/126
24
+
5
25
  ## Ruby D-Bus 0.19.0 - 2023-01-18
6
26
 
7
27
  API:
@@ -73,7 +93,7 @@ API:
73
93
  when declaring properties ([#117][]).
74
94
 
75
95
  [#115]: https://github.com/mvidner/ruby-dbus/issues/115
76
- [#117]: https://github.com/mvidner/ruby-dbus/pulls/117
96
+ [#117]: https://github.com/mvidner/ruby-dbus/pull/117
77
97
 
78
98
  ## Ruby D-Bus 0.18.0.beta7 - 2022-05-29
79
99
 
@@ -441,7 +461,7 @@ Bug fixes:
441
461
  * Handle more ways which tell us that a bus connection has died.
442
462
 
443
463
  [#3]: https://github.com/mvidner/ruby-dbus/issue/3
444
- [bsc#617350]: https://bugzilla.novell.com/show_bug.cgi?id=617350
464
+ [bsc#617350]: https://bugzilla.suse.com/show_bug.cgi?id=617350
445
465
 
446
466
  ## Ruby D-Bus 0.3.0 - 2010-03-28
447
467
 
@@ -509,8 +529,8 @@ Bug fixes:
509
529
  * Fixed an endless sleep in DBus::Main.run ([bsc#537401][]).
510
530
  * Added details to PacketMarshaller exceptions ([bsc#538050][]).
511
531
 
512
- [bsc#537401]: https://bugzilla.novell.com/show_bug.cgi?id=537401
513
- [bsc#538050]: https://bugzilla.novell.com/show_bug.cgi?id=538050
532
+ [bsc#537401]: https://bugzilla.suse.com/show_bug.cgi?id=537401
533
+ [bsc#538050]: https://bugzilla.suse.com/show_bug.cgi?id=538050
514
534
 
515
535
  ## Ruby D-Bus "I'm not dead" 0.2.9 - 2009-08-26
516
536
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.19.0
1
+ 0.21.0
@@ -6,7 +6,9 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
6
6
 
7
7
  require "dbus"
8
8
 
9
- bus = DBus::SystemBus.instance
9
+ busname = ARGV.fetch(0, "system")
10
+ bus = busname == "session" ? DBus::SessionBus.instance : DBus::SystemBus.instance
11
+
10
12
  driver_svc = bus["org.freedesktop.DBus"]
11
13
  # p driver_svc
12
14
  driver_obj = driver_svc["/"]
@@ -15,4 +17,4 @@ driver_ifc = driver_obj["org.freedesktop.DBus"]
15
17
  # p driver_ifc
16
18
 
17
19
  bus_id = driver_ifc.GetId
18
- puts "The system bus id is #{bus_id}"
20
+ puts "The #{busname} bus id is #{bus_id}"
data/lib/dbus/auth.rb CHANGED
@@ -12,261 +12,348 @@ require "rbconfig"
12
12
 
13
13
  module DBus
14
14
  # Exception raised when authentication fails somehow.
15
- class AuthenticationFailed < Exception
15
+ class AuthenticationFailed < StandardError
16
16
  end
17
17
 
18
- # = General class for authentication.
19
- class Authenticator
20
- # Returns the name of the authenticator.
21
- def name
22
- self.class.to_s.upcase.sub(/.*::/, "")
23
- end
24
- end
18
+ # The Authentication Protocol.
19
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
20
+ #
21
+ # @api private
22
+ module Authentication
23
+ # Base class of authentication mechanisms
24
+ class Mechanism
25
+ # @!method call(challenge)
26
+ # @abstract
27
+ # Replies to server *challenge*, or sends an initial response if the challenge is `nil`.
28
+ # @param challenge [String,nil]
29
+ # @return [Array(Symbol,String)] pair [action, response], where
30
+ # - [:MechContinue, response] caller should send "DATA response" and go to :WaitingForData
31
+ # - [:MechOk, response] caller should send "DATA response" and go to :WaitingForOk
32
+ # - [:MechError, message] caller should send "ERROR message" and go to :WaitingForData
25
33
 
26
- # = Anonymous authentication class
27
- class Anonymous < Authenticator
28
- def authenticate
29
- "527562792044427573" # Hex encoded version of "Ruby DBus"
34
+ # Uppercase mechanism name, as sent to the server
35
+ # @return [String]
36
+ def name
37
+ self.class.to_s.upcase.sub(/.*::/, "")
38
+ end
30
39
  end
31
- end
32
40
 
33
- # = External authentication class
34
- #
35
- # Class for 'external' type authentication.
36
- class External < Authenticator
37
- # Performs the authentication.
38
- def authenticate
39
- # Take the user id (eg integer 1000) make a string out of it "1000", take
40
- # each character and determin hex value "1" => 0x31, "0" => 0x30. You
41
- # obtain for "1000" => 31303030 This is what the server is expecting.
42
- # Why? I dunno. How did I come to that conclusion? by looking at rbus
43
- # code. I have no idea how he found that out.
44
- Process.uid.to_s.split(//).map { |d| d.ord.to_s(16) }.join
41
+ # Anonymous authentication class.
42
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-anonymous
43
+ class Anonymous < Mechanism
44
+ def call(_challenge)
45
+ [:MechOk, "Ruby DBus"]
46
+ end
45
47
  end
46
- end
47
48
 
48
- # = Authentication class using SHA1 crypto algorithm
49
- #
50
- # Class for 'CookieSHA1' type authentication.
51
- # Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
52
- class DBusCookieSHA1 < Authenticator
53
- # the autenticate method (called in stage one of authentification)
54
- def authenticate
55
- require "etc"
56
- # number of retries we have for auth
57
- @retries = 1
58
- hex_encode(Etc.getlogin).to_s # server expects it to be binary
49
+ # Class for 'external' type authentication.
50
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-external
51
+ class External < Mechanism
52
+ # Performs the authentication.
53
+ def call(_challenge)
54
+ [:MechOk, Process.uid.to_s]
55
+ end
59
56
  end
60
57
 
61
- # returns the modules name
62
- def name
63
- "DBUS_COOKIE_SHA1"
58
+ # A variant of EXTERNAL that doesn't say our UID.
59
+ # Seen busctl do this and it worked across a container boundary.
60
+ class ExternalWithoutUid < External
61
+ def name
62
+ "EXTERNAL"
63
+ end
64
+
65
+ def call(_challenge)
66
+ [:MechContinue, nil]
67
+ end
64
68
  end
65
69
 
66
- # handles the interesting crypto stuff, check the rbus-project for more info: http://rbus.rubyforge.org/
67
- def data(hexdata)
68
- require "digest/sha1"
69
- data = hex_decode(hexdata)
70
- # name of cookie file, id of cookie in file, servers random challenge
71
- context, id, s_challenge = data.split(" ")
72
- # Random client challenge
73
- c_challenge = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join
74
- # Search cookie file for id
75
- path = File.join(ENV["HOME"], ".dbus-keyrings", context)
76
- DBus.logger.debug "path: #{path.inspect}"
77
- File.foreach(path) do |line|
78
- if line.start_with?(id)
79
- # Right line of file, read cookie
80
- cookie = line.split(" ")[2].chomp
81
- DBus.logger.debug "cookie: #{cookie.inspect}"
82
- # Concatenate and encrypt
83
- to_encrypt = [s_challenge, c_challenge, cookie].join(":")
84
- sha = Digest::SHA1.hexdigest(to_encrypt)
85
- # the almighty tcp server wants everything hex encoded
86
- hex_response = hex_encode("#{c_challenge} #{sha}")
87
- # Return response
88
- response = [:AuthOk, hex_response]
89
- return response
70
+ # Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
71
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha
72
+ class DBusCookieSHA1 < Mechanism
73
+ # returns the modules name
74
+ def name
75
+ "DBUS_COOKIE_SHA1"
76
+ end
77
+
78
+ # First we are called with nil and we reply with our username.
79
+ # Then we prove that we can read that user's cookie file.
80
+ def call(challenge)
81
+ if challenge.nil?
82
+ require "etc"
83
+ # number of retries we have for auth
84
+ @retries = 1
85
+ return [:MechContinue, Etc.getlogin]
86
+ end
87
+
88
+ require "digest/sha1"
89
+ # name of cookie file, id of cookie in file, servers random challenge
90
+ context, id, s_challenge = challenge.split(" ")
91
+ # Random client challenge
92
+ c_challenge = 1.upto(s_challenge.bytesize / 2).map { rand(255).to_s }.join
93
+ # Search cookie file for id
94
+ path = File.join(ENV["HOME"], ".dbus-keyrings", context)
95
+ DBus.logger.debug "path: #{path.inspect}"
96
+ File.foreach(path) do |line|
97
+ if line.start_with?(id)
98
+ # Right line of file, read cookie
99
+ cookie = line.split(" ")[2].chomp
100
+ DBus.logger.debug "cookie: #{cookie.inspect}"
101
+ # Concatenate and encrypt
102
+ to_encrypt = [s_challenge, c_challenge, cookie].join(":")
103
+ sha = Digest::SHA1.hexdigest(to_encrypt)
104
+ # Return response
105
+ response = [:MechOk, "#{c_challenge} #{sha}"]
106
+ return response
107
+ end
90
108
  end
109
+ return if @retries <= 0
110
+
111
+ # a little rescue magic
112
+ puts "ERROR: Could not auth, will now exit."
113
+ puts "ERROR: Unable to locate cookie, retry in 1 second."
114
+ @retries -= 1
115
+ sleep 1
116
+ call(challenge)
91
117
  end
92
- return if @retries <= 0
93
-
94
- # a little rescue magic
95
- puts "ERROR: Could not auth, will now exit."
96
- puts "ERROR: Unable to locate cookie, retry in 1 second."
97
- @retries -= 1
98
- sleep 1
99
- data(hexdata)
100
118
  end
101
119
 
102
- # encode plain to hex
103
- def hex_encode(plain)
104
- return nil if plain.nil?
120
+ # Declare client state transitions, for ease of code reading.
121
+ # It is just a pair.
122
+ NextState = Struct.new(:state, :command_words)
105
123
 
106
- plain.to_s.unpack1("H*")
107
- end
124
+ # Authenticates the connection before messages can be exchanged.
125
+ class Client
126
+ # @return [Boolean] have we negotiated Unix file descriptor passing
127
+ # NOTE: not implemented yet in upper layers
128
+ attr_reader :unix_fd
108
129
 
109
- # decode hex to plain
110
- def hex_decode(encoded)
111
- encoded.scan(/[[:xdigit:]]{2}/).map { |h| h.hex.chr }.join
112
- end
113
- end
130
+ # @return [String]
131
+ attr_reader :address_uuid
114
132
 
115
- # Note: this following stuff is tested with External authenticator only!
133
+ # Create a new authentication client.
134
+ # @param mechs [Array<Mechanism,Class>,nil] custom list of auth Mechanism objects or classes
135
+ def initialize(socket, mechs = nil)
136
+ @unix_fd = false
137
+ @address_uuid = nil
116
138
 
117
- # = Authentication client class.
118
- #
119
- # Class tha performs the actional authentication.
120
- class Client
121
- # Create a new authentication client.
122
- def initialize(socket)
123
- @socket = socket
124
- @state = nil
125
- @auth_list = [External, DBusCookieSHA1, Anonymous]
126
- end
139
+ @socket = socket
140
+ @state = nil
141
+ @auth_list = mechs || [
142
+ External,
143
+ DBusCookieSHA1,
144
+ ExternalWithoutUid,
145
+ Anonymous
146
+ ]
147
+ end
148
+
149
+ # Start the authentication process.
150
+ # @return [void]
151
+ # @raise [AuthenticationFailed]
152
+ def authenticate
153
+ DBus.logger.debug "Authenticating"
154
+ send_nul_byte
127
155
 
128
- # Start the authentication process.
129
- def authenticate
130
- if RbConfig::CONFIG["target_os"] =~ /freebsd/
131
- @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""])
132
- else
133
- @socket.write(0.chr)
156
+ use_next_mechanism
157
+
158
+ @state, command = next_state_via_mechanism.to_a
159
+ send(command)
160
+
161
+ loop do
162
+ DBus.logger.debug "auth STATE: #{@state}"
163
+ words = next_msg
164
+
165
+ @state, command = next_state(words).to_a
166
+ break if [:TerminatedOk, :TerminatedError].include? @state
167
+
168
+ send(command)
169
+ end
170
+
171
+ raise AuthenticationFailed, command.first if @state == :TerminatedError
172
+
173
+ send("BEGIN")
134
174
  end
135
- next_authenticator
136
- @state = :Starting
137
- while @state != :Authenticated
138
- r = next_state
139
- return r if !r
175
+
176
+ ##########
177
+
178
+ private
179
+
180
+ ##########
181
+
182
+ # The authentication protocol requires a nul byte
183
+ # that may carry credentials.
184
+ # @return [void]
185
+ def send_nul_byte
186
+ if RbConfig::CONFIG["target_os"] =~ /freebsd/
187
+ @socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""])
188
+ else
189
+ @socket.write(0.chr)
190
+ end
140
191
  end
141
- true
142
- end
143
192
 
144
- ##########
193
+ # encode plain to hex
194
+ # @param plain [String,nil]
195
+ # @return [String,nil]
196
+ def hex_encode(plain)
197
+ return nil if plain.nil?
145
198
 
146
- private
199
+ plain.unpack1("H*")
200
+ end
147
201
 
148
- ##########
202
+ # decode hex to plain
203
+ # @param encoded [String,nil]
204
+ # @return [String,nil]
205
+ def hex_decode(encoded)
206
+ return nil if encoded.nil?
149
207
 
150
- # Send an authentication method _meth_ with arguments _args_ to the
151
- # server.
152
- def send(meth, *args)
153
- o = ([meth] + args).join(" ")
154
- @socket.write("#{o}\r\n")
155
- end
208
+ [encoded].pack("H*")
209
+ end
156
210
 
157
- # Try authentication using the next authenticator.
158
- def next_authenticator
159
- raise AuthenticationFailed if @auth_list.empty?
160
-
161
- @authenticator = @auth_list.shift.new
162
- auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate]
163
- DBus.logger.debug "auth_msg: #{auth_msg.inspect}"
164
- send(auth_msg)
165
- rescue AuthenticationFailed
166
- @socket.close
167
- raise
168
- end
211
+ # Send a string to the socket; good place for test mocks.
212
+ def write_line(str)
213
+ DBus.logger.debug "auth_write: #{str.inspect}"
214
+ @socket.write(str)
215
+ end
169
216
 
170
- # Read data (a buffer) from the bus until CR LF is encountered.
171
- # Return the buffer without the CR LF characters.
172
- def next_msg
173
- data = ""
174
- crlf = "\r\n"
175
- left = 1024 # 1024 byte, no idea if it's ever getting bigger
176
- while left.positive?
177
- buf = @socket.read(left > 1 ? 1 : left)
178
- break if buf.nil?
179
-
180
- left -= buf.bytesize
181
- data += buf
182
- break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break
217
+ # Send *words* to the server as a single CRLF terminated string.
218
+ # @param words [Array<String>,String]
219
+ def send(words)
220
+ joined = Array(words).compact.join(" ")
221
+ write_line("#{joined}\r\n")
183
222
  end
184
- readline = data.chomp.split(" ")
185
- DBus.logger.debug "readline: #{readline.inspect}"
186
- readline
187
- end
188
223
 
189
- # # Read data (a buffer) from the bus until CR LF is encountered.
190
- # # Return the buffer without the CR LF characters.
191
- # def next_msg
192
- # @socket.readline.chomp.split(" ")
193
- # end
194
-
195
- # Try to reach the next state based on the current state.
196
- def next_state
197
- msg = next_msg
198
- if @state == :Starting
199
- DBus.logger.debug ":Starting msg: #{msg[0].inspect}"
200
- case msg[0]
201
- when "OK"
202
- @state = :WaitingForOk
203
- when "CONTINUE"
204
- @state = :WaitingForData
205
- when "REJECTED" # needed by tcp, unix-path/abstract doesn't get here
206
- @state = :WaitingForData
207
- end
224
+ # Try authentication using the next mechanism.
225
+ # @raise [AuthenticationFailed] if there are no more left
226
+ # @return [void]
227
+ def use_next_mechanism
228
+ raise AuthenticationFailed, "Authentication mechanisms exhausted" if @auth_list.empty?
229
+
230
+ @mechanism = @auth_list.shift
231
+ @mechanism = @mechanism.new if @mechanism.is_a? Class
232
+ rescue AuthenticationFailed
233
+ # TODO: make this caller's responsibility
234
+ @socket.close
235
+ raise
208
236
  end
209
- DBus.logger.debug "state: #{@state}"
210
- case @state
211
- when :WaitingForData
212
- DBus.logger.debug ":WaitingForData msg: #{msg[0].inspect}"
213
- case msg[0]
214
- when "DATA"
215
- chall = msg[1]
216
- resp, chall = @authenticator.data(chall)
217
- DBus.logger.debug ":WaitingForData/DATA resp: #{resp.inspect}"
218
- case resp
219
- when :AuthContinue
220
- send("DATA", chall)
221
- @state = :WaitingForData
222
- when :AuthOk
223
- send("DATA", chall)
224
- @state = :WaitingForOk
225
- when :AuthError
226
- send("ERROR")
227
- @state = :WaitingForData
228
- end
229
- when "REJECTED"
230
- next_authenticator
231
- @state = :WaitingForData
232
- when "ERROR"
233
- send("CANCEL")
234
- @state = :WaitingForReject
235
- when "OK"
236
- send("BEGIN")
237
- @state = :Authenticated
238
- else
239
- send("ERROR")
240
- @state = :WaitingForData
237
+
238
+ # Read data (a buffer) from the bus until CR LF is encountered.
239
+ # Return the buffer without the CR LF characters.
240
+ # @return [Array<String>] received words
241
+ def next_msg
242
+ read_line.chomp.split(" ")
243
+ end
244
+
245
+ # Read a line from the socket; good place for test mocks.
246
+ # @return [String] CRLF (\r\n) terminated
247
+ def read_line
248
+ # TODO: probably can simply call @socket.readline
249
+ data = ""
250
+ crlf = "\r\n"
251
+ left = 1024 # 1024 byte, no idea if it's ever getting bigger
252
+ while left.positive?
253
+ buf = @socket.read(left > 1 ? 1 : left)
254
+ break if buf.nil?
255
+
256
+ left -= buf.bytesize
257
+ data += buf
258
+ break if data.include? crlf # crlf means line finished, the TCP socket keeps on listening, so we break
241
259
  end
242
- when :WaitingForOk
243
- DBus.logger.debug ":WaitingForOk msg: #{msg[0].inspect}"
244
- case msg[0]
245
- when "OK"
246
- send("BEGIN")
247
- @state = :Authenticated
248
- when "REJECT"
249
- next_authenticator
250
- @state = :WaitingForData
251
- when "DATA", "ERROR"
252
- send("CANCEL")
253
- @state = :WaitingForReject
260
+ DBus.logger.debug "auth_read: #{data.inspect}"
261
+ data
262
+ end
263
+
264
+ # # Read data (a buffer) from the bus until CR LF is encountered.
265
+ # # Return the buffer without the CR LF characters.
266
+ # def next_msg
267
+ # @socket.readline.chomp.split(" ")
268
+ # end
269
+
270
+ # @param hex_challenge [String,nil] (nil when the server said "DATA\r\n")
271
+ # @param use_data [Boolean] say DATA instead of AUTH
272
+ # @return [NextState]
273
+ def next_state_via_mechanism(hex_challenge = nil, use_data: false)
274
+ challenge = hex_decode(hex_challenge)
275
+
276
+ action, response = @mechanism.call(challenge)
277
+ DBus.logger.debug "auth mechanism action: #{action.inspect}"
278
+
279
+ command = use_data ? ["DATA"] : ["AUTH", @mechanism.name]
280
+
281
+ case action
282
+ when :MechError
283
+ NextState.new(:WaitingForData, ["ERROR", response])
284
+ when :MechContinue
285
+ NextState.new(:WaitingForData, command + [hex_encode(response)])
286
+ when :MechOk
287
+ NextState.new(:WaitingForOk, command + [hex_encode(response)])
254
288
  else
255
- send("ERROR")
256
- @state = :WaitingForOk
289
+ raise AuthenticationFailed, "internal error, unknown action #{action.inspect} " \
290
+ "from our mechanism #{@mechanism.inspect}"
257
291
  end
258
- when :WaitingForReject
259
- DBus.logger.debug ":WaitingForReject msg: #{msg[0].inspect}"
260
- case msg[0]
261
- when "REJECT"
262
- next_authenticator
263
- @state = :WaitingForOk
292
+ end
293
+
294
+ # Try to reach the next state based on the current state.
295
+ # @param received_words [Array<String>]
296
+ # @return [NextState]
297
+ def next_state(received_words)
298
+ msg = received_words
299
+
300
+ case @state
301
+ when :WaitingForData
302
+ case msg[0]
303
+ when "DATA"
304
+ next_state_via_mechanism(msg[1], use_data: true)
305
+ when "REJECTED"
306
+ use_next_mechanism
307
+ next_state_via_mechanism
308
+ when "ERROR"
309
+ NextState.new(:WaitingForReject, ["CANCEL"])
310
+ when "OK"
311
+ @address_uuid = msg[1]
312
+ # NextState.new(:TerminatedOk, [])
313
+ NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"])
314
+ else
315
+ NextState.new(:WaitingForData, ["ERROR"])
316
+ end
317
+ when :WaitingForOk
318
+ case msg[0]
319
+ when "OK"
320
+ @address_uuid = msg[1]
321
+ # NextState.new(:TerminatedOk, [])
322
+ NextState.new(:WaitingForAgreeUnixFD, ["NEGOTIATE_UNIX_FD"])
323
+ when "REJECTED"
324
+ use_next_mechanism
325
+ next_state_via_mechanism
326
+ when "DATA", "ERROR"
327
+ NextState.new(:WaitingForReject, ["CANCEL"])
328
+ else
329
+ # we don't understand server's response but still wait for a successful auth completion
330
+ NextState.new(:WaitingForOk, ["ERROR"])
331
+ end
332
+ when :WaitingForReject
333
+ case msg[0]
334
+ when "REJECTED"
335
+ use_next_mechanism
336
+ next_state_via_mechanism
337
+ else
338
+ # TODO: spec says to close socket, clarify
339
+ NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} when expecting REJECTED"])
340
+ end
341
+ when :WaitingForAgreeUnixFD
342
+ case msg[0]
343
+ when "AGREE_UNIX_FD"
344
+ @unix_fd = true
345
+ NextState.new(:TerminatedOk, [])
346
+ when "ERROR"
347
+ @unix_fd = false
348
+ NextState.new(:TerminatedOk, [])
349
+ else
350
+ # TODO: spec says to close socket, clarify
351
+ NextState.new(:TerminatedError, ["Unknown server reply #{msg[0].inspect} to NEGOTIATE_UNIX_FD"])
352
+ end
264
353
  else
265
- @socket.close
266
- return false
354
+ raise "Internal error: unhandled state #{@state.inspect}"
267
355
  end
268
356
  end
269
- true
270
357
  end
271
358
  end
272
359
  end
data/lib/dbus/message.rb CHANGED
@@ -10,6 +10,8 @@
10
10
  # License, version 2.1 as published by the Free Software Foundation.
11
11
  # See the file "COPYING" for the exact licensing terms.
12
12
 
13
+ require_relative "raw_message"
14
+
13
15
  # = D-Bus main module
14
16
  #
15
17
  # Module containing all the D-Bus modules and classes.
@@ -144,6 +146,10 @@ module DBus
144
146
  @params << [type, val]
145
147
  end
146
148
 
149
+ # "l" or "B"
150
+ ENDIANNESS_CHAR = ENV.fetch("RUBY_DBUS_ENDIANNESS", HOST_END)
151
+ ENDIANNESS = RawMessage.endianness(ENDIANNESS_CHAR)
152
+
147
153
  # FIXME: what are these? a message element constant enumeration?
148
154
  # See method below, in a message, you have and array of optional parameters
149
155
  # that come with an index, to determine their meaning. The values are in
@@ -165,14 +171,14 @@ module DBus
165
171
  raise InvalidDestinationName
166
172
  end
167
173
 
168
- params = PacketMarshaller.new
169
- @params.each do |param|
170
- params.append(param[0], param[1])
174
+ params_marshaller = PacketMarshaller.new(endianness: ENDIANNESS)
175
+ @params.each do |type, value|
176
+ params_marshaller.append(type, value)
171
177
  end
172
- @body_length = params.packet.bytesize
178
+ @body_length = params_marshaller.packet.bytesize
173
179
 
174
- marshaller = PacketMarshaller.new
175
- marshaller.append(Type::BYTE, HOST_END.ord)
180
+ marshaller = PacketMarshaller.new(endianness: ENDIANNESS)
181
+ marshaller.append(Type::BYTE, ENDIANNESS_CHAR.ord)
176
182
  marshaller.append(Type::BYTE, @message_type)
177
183
  marshaller.append(Type::BYTE, @flags)
178
184
  marshaller.append(Type::BYTE, @protocol)
@@ -191,10 +197,8 @@ module DBus
191
197
  marshaller.append("a(yv)", headers)
192
198
 
193
199
  marshaller.align(8)
194
- @params.each do |param|
195
- marshaller.append(param[0], param[1])
196
- end
197
- marshaller.packet
200
+
201
+ marshaller.packet + params_marshaller.packet
198
202
  end
199
203
 
200
204
  # Unmarshall a packet contained in the buffer _buf_ and set the
@@ -18,10 +18,17 @@ module DBus
18
18
  # The socket that is used to connect with the bus.
19
19
  attr_reader :socket
20
20
 
21
+ # The buffer size for messages.
22
+ MSG_BUF_SIZE = 4096
23
+
21
24
  def initialize(address)
25
+ DBus.logger.debug "MessageQueue: #{address}"
22
26
  @address = address
23
27
  @buffer = ""
28
+ # Reduce allocations by using a single buffer for our socket
29
+ @read_buffer = String.new(capacity: MSG_BUF_SIZE)
24
30
  @is_tcp = false
31
+ @mutex = Mutex.new
25
32
  connect
26
33
  end
27
34
 
@@ -32,23 +39,28 @@ module DBus
32
39
  # @raise EOFError
33
40
  # @todo failure modes
34
41
  def pop(blocking: true)
35
- buffer_from_socket_nonblock
36
- message = message_from_buffer_nonblock
37
- if blocking
38
- # we can block
39
- while message.nil?
40
- r, _d, _d = IO.select([@socket])
41
- if r && r[0] == @socket
42
- buffer_from_socket_nonblock
43
- message = message_from_buffer_nonblock
42
+ # FIXME: this is not enough, the R/W test deadlocks on shared connections
43
+ @mutex.synchronize do
44
+ buffer_from_socket_nonblock
45
+ message = message_from_buffer_nonblock
46
+ if blocking
47
+ # we can block
48
+ while message.nil?
49
+ r, _d, _d = IO.select([@socket])
50
+ if r && r[0] == @socket
51
+ buffer_from_socket_nonblock
52
+ message = message_from_buffer_nonblock
53
+ end
44
54
  end
45
55
  end
56
+ message
46
57
  end
47
- message
48
58
  end
49
59
 
50
60
  def push(message)
51
- @socket.write(message.marshall)
61
+ @mutex.synchronize do
62
+ @socket.write(message.marshall)
63
+ end
52
64
  end
53
65
  alias << push
54
66
 
@@ -129,7 +141,7 @@ module DBus
129
141
 
130
142
  # Initialize the connection to the bus.
131
143
  def init_connection
132
- client = Client.new(@socket)
144
+ client = Authentication::Client.new(@socket)
133
145
  client.authenticate
134
146
  end
135
147
 
@@ -150,15 +162,12 @@ module DBus
150
162
  ret
151
163
  end
152
164
 
153
- # The buffer size for messages.
154
- MSG_BUF_SIZE = 4096
155
-
156
165
  # Fill (append) the buffer from data that might be available on the
157
166
  # socket.
158
167
  # @return [void]
159
168
  # @raise EOFError
160
169
  def buffer_from_socket_nonblock
161
- @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
170
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE, @read_buffer)
162
171
  rescue EOFError
163
172
  raise # the caller expects it
164
173
  rescue Errno::EAGAIN
data/lib/dbus.rb CHANGED
@@ -10,6 +10,21 @@
10
10
  # License, version 2.1 as published by the Free Software Foundation.
11
11
  # See the file "COPYING" for the exact licensing terms.
12
12
 
13
+ module DBus
14
+ # Byte signifying big endianness.
15
+ BIG_END = "B"
16
+ # Byte signifying little endianness.
17
+ LIL_END = "l"
18
+
19
+ # Byte signifying the host's endianness.
20
+ HOST_END = if [0x01020304].pack("L").unpack1("V") == 0x01020304
21
+ LIL_END
22
+ else
23
+ BIG_END
24
+ end
25
+ end
26
+ # ^ That's because dbus/message needs HOST_END early
27
+
13
28
  require_relative "dbus/api_options"
14
29
  require_relative "dbus/auth"
15
30
  require_relative "dbus/bus"
@@ -41,18 +56,6 @@ module DBus
41
56
  # Default socket name for the system bus.
42
57
  SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"
43
58
 
44
- # Byte signifying big endianness.
45
- BIG_END = "B"
46
- # Byte signifying little endianness.
47
- LIL_END = "l"
48
-
49
- # Byte signifying the host's endianness.
50
- HOST_END = if [0x01020304].pack("L").unpack1("V") == 0x01020304
51
- LIL_END
52
- else
53
- BIG_END
54
- end
55
-
56
59
  # Comparing symbols is faster than strings
57
60
  # @return [:little,:big]
58
61
  HOST_ENDIANNESS = RawMessage.endianness(HOST_END)
data/spec/auth_spec.rb ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::Authentication::Client do
8
+ let(:socket) { instance_double("Socket") }
9
+ let(:subject) { described_class.new(socket) }
10
+
11
+ before(:each) do
12
+ allow(Process).to receive(:uid).and_return(999)
13
+ allow(subject).to receive(:send_nul_byte)
14
+ end
15
+
16
+ describe "#next_state" do
17
+ it "raises when I forget to handle a state" do
18
+ subject.instance_variable_set(:@state, :Denmark)
19
+ expect { subject.__send__(:next_state, []) }.to raise_error(RuntimeError, /unhandled state :Denmark/)
20
+ end
21
+ end
22
+
23
+ def expect_protocol(pairs)
24
+ pairs.each do |we_say, server_says|
25
+ expect(subject).to receive(:write_line).with(we_say)
26
+ next if server_says.nil?
27
+
28
+ expect(subject).to receive(:read_line).and_return(server_says)
29
+ end
30
+ end
31
+
32
+ context "with ANONYMOUS" do
33
+ let(:subject) { described_class.new(socket, [DBus::Authentication::Anonymous]) }
34
+
35
+ it "authentication passes" do
36
+ expect_protocol [
37
+ ["AUTH ANONYMOUS 527562792044427573\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
38
+ ["NEGOTIATE_UNIX_FD\r\n", "ERROR not for anonymous\r\n"],
39
+ ["BEGIN\r\n"]
40
+ ]
41
+
42
+ expect { subject.authenticate }.to_not raise_error
43
+ end
44
+ end
45
+
46
+ context "with EXTERNAL" do
47
+ let(:subject) { described_class.new(socket, [DBus::Authentication::External]) }
48
+
49
+ it "authentication passes, and address_uuid is set" do
50
+ expect_protocol [
51
+ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
52
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
53
+ ["BEGIN\r\n"]
54
+ ]
55
+
56
+ expect { subject.authenticate }.to_not raise_error
57
+ expect(subject.address_uuid).to eq "ffffffffffffffffffffffffffffffff"
58
+ end
59
+
60
+ context "when the server says superfluous things before an OK" do
61
+ it "authentication passes" do
62
+ expect_protocol [
63
+ ["AUTH EXTERNAL 393939\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
64
+ ["ERROR\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
65
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
66
+ ["BEGIN\r\n"]
67
+ ]
68
+
69
+ expect { subject.authenticate }.to_not raise_error
70
+ end
71
+ end
72
+
73
+ context "when the server messes up NEGOTIATE_UNIX_FD" do
74
+ it "authentication fails orderly" do
75
+ expect_protocol [
76
+ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
77
+ ["NEGOTIATE_UNIX_FD\r\n", "I_DONT_NEGOTIATE_WITH_TENORISTS\r\n"]
78
+ ]
79
+
80
+ allow(socket).to receive(:close) # want to get rid of this
81
+ # TODO: quote the server error message?
82
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown server reply/)
83
+ end
84
+ end
85
+
86
+ context "when the server replies with ERROR" do
87
+ it "authentication fails orderly" do
88
+ expect_protocol [
89
+ ["AUTH EXTERNAL 393939\r\n", "ERROR something failed\r\n"],
90
+ ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
91
+ ]
92
+
93
+ allow(socket).to receive(:close) # want to get rid of this
94
+ # TODO: quote the server error message?
95
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
96
+ end
97
+ end
98
+ end
99
+
100
+ context "with EXTERNAL without uid" do
101
+ let(:subject) do
102
+ described_class.new(socket, [DBus::Authentication::External, DBus::Authentication::ExternalWithoutUid])
103
+ end
104
+
105
+ it "authentication passes" do
106
+ expect_protocol [
107
+ ["AUTH EXTERNAL 393939\r\n", "REJECTED EXTERNAL\r\n"],
108
+ # this succeeds when we connect to a privileged container,
109
+ # where outside-non-root becomes inside-root
110
+ ["AUTH EXTERNAL\r\n", "DATA\r\n"],
111
+ ["DATA\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
112
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
113
+ ["BEGIN\r\n"]
114
+ ]
115
+
116
+ expect { subject.authenticate }.to_not raise_error
117
+ end
118
+ end
119
+
120
+ context "with a rejected mechanism and then EXTERNAL" do
121
+ let(:rejected_mechanism) do
122
+ double("Mechanism", name: "WIMP", call: [:MechContinue, "I expect to be rejected"])
123
+ end
124
+
125
+ let(:subject) { described_class.new(socket, [rejected_mechanism, DBus::Authentication::External]) }
126
+
127
+ it "authentication eventually passes" do
128
+ expect_protocol [
129
+ [/^AUTH WIMP .*\r\n/, "REJECTED EXTERNAL\r\n"],
130
+ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
131
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
132
+ ["BEGIN\r\n"]
133
+ ]
134
+
135
+ expect { subject.authenticate }.to_not raise_error
136
+ end
137
+ end
138
+
139
+ context "with a DATA-using mechanism" do
140
+ let(:mechanism) do
141
+ double("Mechanism", name: "CHALLENGE_ME", call: [:MechContinue, "1"])
142
+ end
143
+
144
+ # try it twice to test calling #use_next_mechanism
145
+ let(:subject) { described_class.new(socket, [mechanism, mechanism]) }
146
+
147
+ it "authentication fails orderly when the server says ERROR" do
148
+ expect_protocol [
149
+ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
150
+ ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"],
151
+ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
152
+ ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
153
+ ]
154
+
155
+ allow(socket).to receive(:close) # want to get rid of this
156
+ # TODO: quote the server error message?
157
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
158
+ end
159
+
160
+ it "authentication fails orderly when the server says ERROR and then changes its mind" do
161
+ expect_protocol [
162
+ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
163
+ ["CANCEL\r\n", "I_CHANGED_MY_MIND please come back\r\n"]
164
+ ]
165
+
166
+ allow(socket).to receive(:close) # want to get rid of this
167
+ # TODO: quote the server error message?
168
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown.*MIND.*REJECTED/)
169
+ end
170
+
171
+ it "authentication passes when the server says superfluous things before DATA" do
172
+ expect_protocol [
173
+ ["AUTH CHALLENGE_ME 31\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
174
+ ["ERROR\r\n", "DATA\r\n"],
175
+ ["DATA 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
176
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
177
+ ["BEGIN\r\n"]
178
+ ]
179
+
180
+ expect { subject.authenticate }.to_not raise_error
181
+ end
182
+
183
+ it "authentication passes when the server decides not to need the DATA" do
184
+ expect_protocol [
185
+ ["AUTH CHALLENGE_ME 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
186
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
187
+ ["BEGIN\r\n"]
188
+ ]
189
+
190
+ expect { subject.authenticate }.to_not raise_error
191
+ end
192
+ end
193
+
194
+ context "with a mechanism returning :MechError" do
195
+ let(:fallible_mechanism) do
196
+ double(name: "FALLIBLE", call: [:MechError, "not my best day"])
197
+ end
198
+
199
+ let(:subject) { described_class.new(socket, [fallible_mechanism]) }
200
+
201
+ it "authentication fails orderly" do
202
+ expect_protocol [
203
+ ["ERROR not my best day\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
204
+ ]
205
+
206
+ allow(socket).to receive(:close) # want to get rid of thise
207
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
208
+ end
209
+ end
210
+
211
+ context "with a badly implemented mechanism" do
212
+ let(:buggy_mechanism) do
213
+ double(name: "buggy", call: [:smurf, nil])
214
+ end
215
+
216
+ let(:subject) { described_class.new(socket, [buggy_mechanism]) }
217
+
218
+ it "authentication fails before protoxol is exchanged" do
219
+ expect(subject).to_not receive(:write_line)
220
+ expect(subject).to_not receive(:read_line)
221
+
222
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /smurf/)
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::ProxyObjectInterface do
8
+ # TODO: tag tests that need a service, eg "needs-service"
9
+ # TODO: maybe remove this and rely on a packaged tool
10
+ around(:each) do |example|
11
+ with_private_bus do
12
+ with_service_by_activation(&example)
13
+ end
14
+ end
15
+
16
+ let(:bus) { DBus::ASessionBus.new }
17
+
18
+ context "when calling org.ruby.service" do
19
+ let(:svc) { bus["org.ruby.service"] }
20
+
21
+ # This is white box testing, knowing the implementation
22
+ # A better way would be structuring it according to the D-Bus Spec
23
+ # Or testing the service side doing the right thing? (What if our bugs cancel out)
24
+ describe "#define_method_from_descriptor" do
25
+ it "can call a method with multiple OUT arguments" do
26
+ obj = svc["/org/ruby/MyInstance"]
27
+ ifc = obj["org.ruby.SampleInterface"]
28
+
29
+ even, odd = ifc.EvenOdd([3, 1, 4, 1, 5, 9, 2, 6])
30
+ expect(even).to eq [4, 2, 6]
31
+ expect(odd).to eq [3, 1, 1, 5, 9]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ # Pedantic full coverage test.
8
+ # The happy paths are covered via calling classes
9
+ describe DBus::RawMessage do
10
+ describe ".endianness" do
11
+ it "returns :little for 'l'" do
12
+ expect(described_class.endianness("l")).to eq :little
13
+ end
14
+
15
+ it "returns :big for 'B'" do
16
+ expect(described_class.endianness("B")).to eq :big
17
+ end
18
+
19
+ it "raises for other strings" do
20
+ expect { described_class.endianness("m") }
21
+ .to raise_error(DBus::InvalidPacketException, /Incorrect endianness/)
22
+ end
23
+ end
24
+
25
+ describe "#align" do
26
+ it "raises for values other than 1 2 4 8" do
27
+ subject = described_class.new("l")
28
+ expect { subject.align(3) }.to raise_error(ArgumentError)
29
+ expect { subject.align(16) }.to raise_error(ArgumentError)
30
+ end
31
+ end
32
+ end
@@ -102,6 +102,12 @@ class Test < DBus::Object
102
102
  [coords]
103
103
  end
104
104
 
105
+ # Two OUT arguments
106
+ dbus_method :EvenOdd, "in numbers:ai, out even:ai, out odd:ai" do |numbers|
107
+ even, odd = numbers.partition(&:even?)
108
+ [even, odd]
109
+ end
110
+
105
111
  # Properties:
106
112
  # ReadMe:string, returns "READ ME" at first, then what WriteMe received
107
113
  # WriteMe:string
data/spec/spec_helper.rb CHANGED
@@ -31,7 +31,7 @@ if coverage
31
31
  c.single_report_path = "coverage/lcov.info"
32
32
  end
33
33
 
34
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
34
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [
35
35
  SimpleCov::Formatter::HTMLFormatter,
36
36
  SimpleCov::Formatter::LcovFormatter
37
37
  ]
@@ -5,28 +5,71 @@
5
5
  require_relative "spec_helper"
6
6
  require "dbus"
7
7
 
8
- describe "ThreadSafetyTest" do
9
- it "tests thread competition" do
10
- print "Thread competition: "
11
- jobs = []
12
- 5.times do
13
- jobs << Thread.new do
14
- Thread.current.abort_on_exception = true
8
+ class TestSignalRace < DBus::Object
9
+ dbus_interface "org.ruby.ServerTest" do
10
+ dbus_signal :signal_without_arguments
11
+ end
12
+ end
13
+
14
+ # Run *count* threads all doing *body*, wait for their finish
15
+ def race_threads(count, &body)
16
+ jobs = count.times.map do |j|
17
+ Thread.new do
18
+ Thread.current.abort_on_exception = true
19
+
20
+ body.call(j)
21
+ end
22
+ end
23
+ jobs.each(&:join)
24
+ end
25
+
26
+ # Repeat *count* times: { random sleep, *body* }, printing progress
27
+ def repeat_with_jitter(count, &body)
28
+ count.times do |i|
29
+ sleep 0.1 * rand
30
+ print "#{i} "
31
+ $stdout.flush
15
32
 
33
+ body.call
34
+ end
35
+ end
36
+
37
+ describe "thread safety" do
38
+ context "R/W: when the threads call methods with return values" do
39
+ it "it works with separate bus connections" do
40
+ race_threads(5) do |_j|
16
41
  # use separate connections to avoid races
17
42
  bus = DBus::ASessionBus.new
18
43
  svc = bus.service("org.ruby.service")
19
44
  obj = svc.object("/org/ruby/MyInstance")
20
45
  obj.default_iface = "org.ruby.SampleInterface"
21
46
 
22
- 10.times do |i|
23
- print "#{i} "
24
- $stdout.flush
47
+ repeat_with_jitter(10) do
25
48
  expect(obj.the_answer[0]).to eq(42)
26
- sleep 0.1 * rand
27
49
  end
28
50
  end
51
+ puts
52
+ end
53
+ end
54
+
55
+ context "W/O: when the threads only send signals" do
56
+ it "it works with a shared separate bus connection" do
57
+ race_threads(5) do |j|
58
+ # shared connection
59
+ bus = DBus::SessionBus.instance
60
+ # hackish: we do not actually request the name
61
+ svc = DBus::Service.new("org.ruby.server-test#{j}", bus)
62
+
63
+ obj = TestSignalRace.new "/org/ruby/Foo"
64
+ svc.export obj
65
+
66
+ repeat_with_jitter(10) do
67
+ obj.signal_without_arguments
68
+ end
69
+
70
+ svc.unexport(obj)
71
+ end
72
+ puts
29
73
  end
30
- jobs.each(&:join)
31
74
  end
32
75
  end
@@ -7,6 +7,20 @@
7
7
  <!-- Our well-known bus type, don't change this -->
8
8
  <type>session</type>
9
9
 
10
+ <!-- Authentication:
11
+ This was useful during refactoring, but meanwhile RSpec mocking has
12
+ replaced it. -->
13
+ <!-- Explicitly list all known authentication mechanisms,
14
+ their order is not important.
15
+ By default the daemon allows all but this lets me disable some. -->
16
+ <auth>EXTERNAL</auth>
17
+ <auth>DBUS_COOKIE_SHA1</auth>
18
+ <auth>ANONYMOUS</auth>
19
+ <!-- Insecure, other users could call us and exploit debug APIs/bugs -->
20
+ <!--
21
+ <allow_anonymous/>
22
+ -->
23
+
10
24
  <listen>unix:tmpdir=/tmp</listen>
11
25
  <listen>tcp:host=127.0.0.1</listen>
12
26
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-dbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruby DBus Team
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-18 00:00:00.000000000 Z
11
+ date: 2023-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rexml
@@ -165,6 +165,7 @@ files:
165
165
  - lib/dbus/xml.rb
166
166
  - ruby-dbus.gemspec
167
167
  - spec/async_spec.rb
168
+ - spec/auth_spec.rb
168
169
  - spec/binding_spec.rb
169
170
  - spec/bus_and_xml_backend_spec.rb
170
171
  - spec/bus_driver_spec.rb
@@ -186,7 +187,9 @@ files:
186
187
  - spec/packet_marshaller_spec.rb
187
188
  - spec/packet_unmarshaller_spec.rb
188
189
  - spec/property_spec.rb
190
+ - spec/proxy_object_interface_spec.rb
189
191
  - spec/proxy_object_spec.rb
192
+ - spec/raw_message_spec.rb
190
193
  - spec/server_robustness_spec.rb
191
194
  - spec/server_spec.rb
192
195
  - spec/service_newapi.rb
@@ -208,7 +211,7 @@ homepage: https://github.com/mvidner/ruby-dbus
208
211
  licenses:
209
212
  - LGPL-2.1
210
213
  metadata: {}
211
- post_install_message:
214
+ post_install_message:
212
215
  rdoc_options: []
213
216
  require_paths:
214
217
  - lib
@@ -223,9 +226,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
226
  - !ruby/object:Gem::Version
224
227
  version: '0'
225
228
  requirements: []
226
- rubyforge_project:
227
- rubygems_version: 2.7.6.3
228
- signing_key:
229
+ rubygems_version: 3.3.0.dev
230
+ signing_key:
229
231
  specification_version: 4
230
232
  summary: Ruby module for interaction with D-Bus
231
233
  test_files: []