File Coverage

blib/lib/Protocol/DBus/Peer.pm
Criterion Covered Total %
statement 96 132 72.7
branch 9 24 37.5
condition 13 24 54.1
subroutine 22 31 70.9
pod 11 13 84.6
total 151 224 67.4


line stmt bran cond sub pod time code
1             package Protocol::DBus::Peer;
2              
3 4     4   1362 use strict;
  4         5  
  4         84  
4 4     4   16 use warnings;
  4         4  
  4         102  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             Protocol::DBus::Peer - base class for a D-Bus peer
11              
12             =head1 SYNOPSIS
13              
14             $dbus->send_call(
15             interface => 'org.freedesktop.DBus.Properties',
16             member => 'GetAll',
17             signature => 's',
18             path => '/org/freedesktop/DBus',
19             destination => 'org.freedesktop.DBus',
20             body => [ 'org.freedesktop.DBus' ],
21             )->then( sub { .. } );
22              
23             my $msg = $dbus->get_message();
24              
25             # Same pattern as the IO::Handle method.
26             $dbus->blocking(0);
27              
28             my $fileno = $dbus->fileno();
29              
30             $dbus->flush_write_queue() if $dbus->pending_send();
31              
32             # I’m not sure why you’d want to do this, but …
33             $dbus->big_endian();
34              
35             =head1 DESCRIPTION
36              
37             This class contains D-Bus logic that is useful in both client and
38             server contexts. (Currently this distribution does not include a server
39             implementation.)
40              
41             =cut
42              
43 4     4   1249 use Call::Context;
  4         1000  
  4         83  
44              
45 4     4   1261 use Protocol::DBus::Message;
  4         11  
  4         109  
46 4     4   1241 use Protocol::DBus::Parser;
  4         8  
  4         88  
47 4     4   1242 use Protocol::DBus::WriteMsg;
  4         10  
  4         97  
48              
49 4     4   22 use constant _PROMISE_CLASS => 'Promise::ES6';
  4         4  
  4         612  
50              
51             #----------------------------------------------------------------------
52              
53             =head1 METHODS
54              
55             =head2 $msg = I->get_message()
56              
57             This returns a single instace of L, or undef if
58             no message is available. It will also fire the appropriate “on_return”
59             method on METHOD_RETURN or ERROR messages.
60              
61             The backend I/O logic reads data in chunks; thus, if there is a message
62             already available in the read buffer, no I/O is done. If you’re doing
63             non-blocking I/O then it is thus B that, every time the DBus socket
64             is readable, you call this function until undef is returned.
65              
66             =cut
67              
68             sub get_message {
69 3     3 1 38 my $msg = $_[0]->{'_parser'}->get_message();
70              
71 3 50       8 if ($msg) {
72 3 100       17 if (my $serial = $msg->get_header('REPLY_SERIAL')) {
73 1         4 delete $_[0]->{'_on_armageddon'}{$serial};
74              
75 1 50       4 if (my $cb = delete $_[0]->{'_on_return'}{$serial}) {
76 1         4 $cb->($msg);
77             }
78             }
79             }
80              
81 3         109 return $msg;
82             }
83              
84             #----------------------------------------------------------------------
85              
86             =head2 I->flush_write_queue()
87              
88             Same as L’s method of the same name.
89              
90             =cut
91              
92             sub flush_write_queue {
93 0 0   0 1 0 if ($_[0]->{'_io'}->get_write_queue_count()) {
94 0         0 return $_[0]->{'_io'}->flush_write_queue();
95             }
96              
97 0         0 return 1;
98             }
99              
100             #----------------------------------------------------------------------
101              
102             =head2 $promise = I->send_call( %OPTS )
103              
104             Send a METHOD_CALL message.
105              
106             %OPTS are C, C, C, C, C,
107             and C. These do as you’d expect, but note that C, if given,
108             must be an array reference.
109              
110             C may be given as an array reference of strings, e.g.,
111             C. See the D-Bus Specification for all possible values.
112              
113             The return value is an instance of L. Normally this promise
114             resolves when a METHOD_RETURN arrives in response. The resolution value is a
115             a L instance that represents the response. If,
116             however, C is given and contains C, the promise
117             resolves as soon as the message is sent.
118              
119             If an ERROR arrives in response instead, the promise will instead reject
120             with a L instance that represents that ERROR.
121             The promise will also reject if some other error happens (e.g., an I/O
122             error while sending the initial METHOD_CALL).
123              
124             =cut
125              
126 4     4   24 use constant _METHOD_RETURN_NUM => Protocol::DBus::Message::Header::MESSAGE_TYPE()->{'METHOD_RETURN'};
  4         4  
  4         4492  
127              
128             sub _get_promise_class {
129 3     3   6 my ($self) = @_;
130              
131 3   66     28 $self->{'_loaded_promise'} ||= do {
132 2         8 local ($!, $@);
133 2         21 my $path = $self->_PROMISE_CLASS() . '.pm';
134 2         13 $path =~ s[::][/]g;
135              
136 2         870 require $path;
137             };
138              
139 3         7109 return $self->_PROMISE_CLASS();
140             }
141              
142             sub send_call {
143 1     1 1 18 my ($self, %opts) = @_;
144              
145 1         4 my ($res, $rej, $response_expected);
146              
147 1         0 my $ok;
148              
149 1         9 my $promise_class = $self->_get_promise_class();
150              
151 1         2 my $serial;
152              
153 1   50     6 my $on_armageddon_hr = $self->{'_on_armageddon'} ||= {};
154              
155             my $promise = $promise_class->new( sub {
156 1     1   32 ($res, $rej) = @_;
157              
158 1 50 33     6 if ($opts{'flags'} && grep { $_ eq 'NO_REPLY_EXPECTED' } @{ $opts{'flags'} }) {
  0         0  
  0         0  
159 0         0 $response_expected = 0;
160             }
161             else {
162 1         2 $response_expected = 1;
163             }
164              
165 1         9 $self->_send_msg(
166             $res,
167             %opts,
168             type => 'METHOD_CALL',
169             );
170              
171 1         52 $serial = $self->{'_last_sent_serial'};
172              
173 1         3 $on_armageddon_hr->{$serial} = $rej;
174              
175 1         2 $ok = 1;
176             } )->finally( sub {
177 1 50   1   42 delete $on_armageddon_hr->{$serial} if $serial;
178 1         391 } );
179              
180 1 50 33     27 if ($ok && $response_expected) {
181             # Keep references to $self out of the callback
182             # in order to avoid memory leaks.
183 1   50     6 my $on_return_hr = $self->{'_on_return'} ||= {};
184              
185 1         1 my $orig_promise = $promise;
186              
187             $promise = $promise->then( sub {
188              
189             return $promise_class->new( sub {
190 1         18 my ($res, $rej) = @_;
191              
192 1         2 $on_armageddon_hr->{$serial} = $rej;
193              
194             $on_return_hr->{$serial} = sub {
195 1 50       5 if ($_[0]->get_type() == _METHOD_RETURN_NUM()) {
196 1         5 $res->($_[0]);
197             }
198             else {
199 0         0 $rej->($_[0]);
200             }
201 1         10 };
202 1     1   47 } );
203 1         14 } );
204             }
205              
206 1         65 return $promise;
207             }
208              
209             =head2 $promise = I->send_return( $ORIG_MSG, %OPTS )
210              
211             Send a METHOD_RETURN message.
212              
213             The return is a promise that resolves when the message is sent.
214              
215             Arguments are similar to C except for the header differences
216             that the D-Bus specification describes. Also, C is not given
217             directly but is instead inferred from the $ORIG_MSG. (Behavior is
218             undefined if this parameter is given directly.)
219              
220             =cut
221              
222             sub send_return {
223 1     1 1 89 my ($self, $orig_msg, @opts_kv) = @_;
224              
225             return $self->_get_promise_class()->new( sub {
226 1     1   19 my ($res) = @_;
227              
228 1         4 $self->_send_msg(
229             $res,
230             _response_fields_from_orig_msg($orig_msg, \@opts_kv),
231             type => 'METHOD_RETURN',
232             );
233 1         4 } );
234             }
235              
236             =head2 $promise = I->send_error( $ORIG_MSG, %OPTS )
237              
238             Like C, but sends an error instead. The
239             C parameter is required.
240              
241             =cut
242              
243             sub send_error {
244 0     0 1 0 my ($self, $orig_msg, @opts_kv) = @_;
245              
246             return $self->_get_promise_class()->new( sub {
247 0     0   0 my ($res) = @_;
248              
249 0         0 $self->_send_msg(
250             $res,
251             _response_fields_from_orig_msg($orig_msg, \@opts_kv),
252             type => 'ERROR',
253             );
254 0         0 } );
255             }
256              
257             sub _response_fields_from_orig_msg {
258              
259             return (
260              
261             # This has to honor a passed “destination”
262             # so that we can implement a D-Bus server in tests.
263             destination => $_[0]->get_header('SENDER'),
264              
265 1     1   3 @{ $_[1] },
  1         5  
266              
267             # Reject callers’ attempts to set this one.
268             reply_serial => $_[0]->get_serial(),
269             );
270             }
271              
272             =head2 $promise = I->send_signal( %OPTS )
273              
274             Like C but sends a signal rather than a method call.
275              
276             =cut
277              
278             sub send_signal {
279 1     1 1 925 my ($self, @opts_kv) = @_;
280              
281             return $self->_get_promise_class()->new( sub {
282 1     1   34 my ($res) = @_;
283              
284 1         4 $self->_send_msg(
285             $res,
286             @opts_kv,
287             type => 'SIGNAL',
288             );
289 1         4 } );
290             }
291              
292             #----------------------------------------------------------------------
293              
294             =head2 I->big_endian()
295              
296             Same interface as C, but this sets/gets/toggles whether to send
297             big-endian messages instead of little-endian.
298              
299             By default this library uses the system’s native byte order, so you probably
300             have little need for this function.
301              
302             =cut
303              
304             sub big_endian {
305 0     0 1 0 my ($self) = @_;
306              
307 0 0       0 if (@_ > 1) {
308 0         0 my $old = $self->{'_big_endian'};
309 0         0 $self->{'_big_endian'} = !!$_[1];
310              
311 0 0       0 $self->{'_to_str_fn'} = 'to_string_' . ($_[1] ? 'be' : 'le');
312              
313 0         0 return $self->{'_big_endian'};
314             }
315              
316 0         0 return !!$self->{'_big_endian'};
317             }
318              
319             #----------------------------------------------------------------------
320              
321             =head2 I->preserve_variant_signatures()
322              
323             Same interface as C, but when this is enabled
324             variants are given as two-member array references ([ signature => value ]),
325             blessed as C instances.
326              
327             For most Perl applications this is probably counterproductive.
328              
329             =cut
330              
331             sub preserve_variant_signatures {
332 0     0 1 0 my $self = shift;
333              
334 0         0 return $self->{'_parser'}->preserve_variant_signatures(@_);
335             }
336              
337             #----------------------------------------------------------------------
338              
339             =head2 I->blocking()
340              
341             Same interface as L’s method of the same name.
342              
343             =cut
344              
345             sub blocking {
346 0     0 1 0 my $self = shift;
347              
348             # require() is needed on pre-5.14 perls:
349 0 0       0 if ($^V lt v5.14) {
350 0         0 local ($@, $!);
351 0         0 require IO::File;
352             }
353              
354 0         0 return $self->{'_socket'}->blocking(@_);
355             }
356              
357             #----------------------------------------------------------------------
358              
359             =head2 I->fileno()
360              
361             Returns the connection socket’s file descriptor.
362              
363             =cut
364              
365             sub fileno {
366 0     0 1 0 return fileno $_[0]->{'_socket'};
367             }
368              
369             #----------------------------------------------------------------------
370              
371             =head2 I->pending_send()
372              
373             Returns a boolean that indicates whether there is data queued up to send
374             to the server.
375              
376             =cut
377              
378             sub pending_send {
379 0     0 1 0 return !!$_[0]->{'_io'}->get_write_queue_count();
380             }
381              
382             #----------------------------------------------------------------------
383              
384             # undocumented
385             sub new {
386 3     3 0 12129 my ($class, $socket) = @_;
387              
388 3         40 my $self = bless { _socket => $socket }, $class;
389              
390 3         70 $self->_set_up_peer_io( $socket );
391              
392 3         6 return $self;
393             }
394              
395             sub do_armageddon {
396 0     0 0 0 my ($self, $why) = @_;
397              
398 0         0 %{ $self->{'_on_return'} } = ();
  0         0  
399              
400 0         0 my $on_armageddon_hr = $self->{'_on_armageddon'};
401              
402 0         0 my @cbs = delete @{$on_armageddon_hr}{ keys %$on_armageddon_hr };
  0         0  
403              
404 0         0 $_->($why) for @cbs;
405              
406 0         0 return;
407             }
408              
409             #----------------------------------------------------------------------
410              
411             sub _set_up_peer_io {
412 3     3   26 my ($self, $socket) = @_;
413              
414 3         142 $self->{'_io'} = Protocol::DBus::WriteMsg->new( $socket )->enable_write_queue();
415 3         126 $self->{'_parser'} = Protocol::DBus::Parser->new( $socket );
416              
417 3         7 return;
418             }
419              
420             sub _send_msg {
421 3     3   44 my ($self, $on_send, %opts) = @_;
422              
423 3         12 my ($type, $body_ar, $flags) = delete @opts{'type', 'body', 'flags'};
424              
425             my @hargs = map {
426 3         10 my $k = $_;
  14         16  
427 14         20 $k =~ tr;
428 14         27 ( $k => $opts{$_} );
429             } keys %opts;
430              
431 3         25 my $serial = ++$self->{'_last_sent_serial'};
432              
433 3         53 my $msg = Protocol::DBus::Message->new(
434             type => $type,
435             hfields => \@hargs,
436             flags => $flags,
437             body => $body_ar,
438             serial => $serial,
439             );
440              
441             # Use native byte order by default.
442 3   100     39 $self->{'_endian'} ||= (pack 'n', 1) eq (pack 'l', 1) ? 'be' : 'le';
443              
444 3   66     26 $self->{'_to_str_fn'} ||= "to_string_$self->{'_endian'}";
445              
446 3         34 my ($buf_sr, $fds_ar) = $msg->can($self->{'_to_str_fn'})->($msg);
447              
448 3 50 66     18 if ($fds_ar && @$fds_ar && !$self->supports_unix_fd()) {
      33        
449 0         0 die "Cannot send file descriptors without UNIX FD support!";
450             }
451              
452 3         24 $self->{'_io'}->enqueue_message( $buf_sr, $fds_ar, $on_send );
453              
454 3         16 return $self->{'_io'}->flush_write_queue();
455             }
456              
457             1;