File Coverage

blib/lib/Tangence/Message.pm
Criterion Covered Total %
statement 280 291 96.2
branch 41 60 68.3
condition 3 6 50.0
subroutine 46 46 100.0
pod 0 20 0.0
total 370 423 87.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2010-2021 -- leonerd@leonerd.org.uk
5              
6 12     12   57749 use v5.26;
  12         41  
7 12     12   929 use Object::Pad 0.41;
  12         12298  
  12         61  
8              
9             package Tangence::Message 0.29;
10             class Tangence::Message;
11              
12 12     12   3309 use Carp;
  12         32  
  12         733  
13              
14 12     12   418 use Tangence::Constants;
  12         22  
  12         1825  
15              
16 12     12   1097 use Tangence::Class;
  12         29  
  12         420  
17 12     12   68 use Tangence::Meta::Method;
  12         20  
  12         316  
18 12     12   70 use Tangence::Meta::Event;
  12         18  
  12         327  
19 12     12   59 use Tangence::Property;
  12         24  
  12         324  
20 12     12   60 use Tangence::Meta::Argument;
  12         23  
  12         370  
21 12     12   1908 use Tangence::Struct;
  12         29  
  12         376  
22 12     12   1079 use Tangence::Types;
  12         19  
  12         747  
23              
24 12     12   1784 use Tangence::Object;
  12         37  
  12         512  
25              
26 12     12   73 use List::Util 1.29 qw( pairmap );
  12         214  
  12         1257  
27 12     12   72 use Scalar::Util qw( weaken blessed );
  12         33  
  12         65455  
28              
29             # Normally we don't care about hash key order. But, when writing test scripts
30             # that will assert on the serialisation bytes, we do. Setting this to some
31             # true value will sort keys first
32             our $SORT_HASH_KEYS = 0;
33              
34 141     141 0 200 has $_stream :param :reader;
  141         321  
35 469     469 0 2217 has $_code :param :reader;
  469         1792  
36 415     415 0 34651 has $_payload :param :reader;
  415         1123  
37              
38 678         821 sub BUILDARGS ( $class, $stream, $code, $payload = "" )
  678         732  
  678         784  
  678         856  
39 678     678 0 18353 {
  678         675  
40 678         3769 return ( stream => $stream, code => $code, payload => $payload );
41             }
42              
43 1847         1842 method _pack_leader ( $type, $num )
  1847         2030  
  1847         1968  
  1847         1883  
44 1847     1847   2527 {
45 1847 100       2593 if( $num < 0x1f ) {
    50          
46 1838         4478 $_payload .= pack( "C", ( $type << 5 ) | $num );
47             }
48             elsif( $num < 0x80 ) {
49 9         37 $_payload .= pack( "CC", ( $type << 5 ) | 0x1f, $num );
50             }
51             else {
52 0         0 $_payload .= pack( "CN", ( $type << 5 ) | 0x1f, $num | 0x80000000 );
53             }
54             }
55              
56             method _peek_leader_type
57 2045     2045   2447 {
58 2045         2084 while(1) {
59 2087 50       3083 length $_payload or croak "Ran out of bytes before finding a leader";
60              
61 2087         3342 my ( $typenum ) = unpack( "C", $_payload );
62 2087         2684 my $type = $typenum >> 5;
63              
64 2087 100       3917 return $type unless $type == DATA_META;
65              
66 42         88 substr( $_payload, 0, 1, "" );
67              
68 42         68 my $num = $typenum & 0x1f;
69 42 100       146 if( $num == DATAMETA_CONSTRUCT ) {
    100          
    50          
70 20         73 $self->unpackmeta_construct;
71             }
72             elsif( $num == DATAMETA_CLASS ) {
73 20         58 $self->unpackmeta_class;
74             }
75             elsif( $num == DATAMETA_STRUCT ) {
76 2         6 $self->unpackmeta_struct;
77             }
78             else {
79 0         0 die sprintf("TODO: Data stream meta-operation 0x%02x", $num);
80             }
81             }
82             }
83              
84 1837         1910 method _unpack_leader ( $peek = 0 )
  1837         1963  
  1837         1840  
85 1837     1837   2376 {
86 1837         2476 my $type = $self->_peek_leader_type;
87 1837         2548 my ( $typenum ) = unpack( "C", $_payload );
88              
89 1837         2228 my $num = $typenum & 0x1f;
90              
91 1837         1933 my $len = 1;
92 1837 100       2759 if( $num == 0x1f ) {
93 9         22 ( $num ) = unpack( "x C", $_payload );
94              
95 9 50       22 if( $num < 0x80 ) {
96 9         13 $len = 2;
97             }
98             else {
99 0         0 ( $num ) = unpack( "x N", $_payload );
100 0         0 $num &= 0x7fffffff;
101 0         0 $len = 5;
102             }
103             }
104              
105 1837 100       3346 substr( $_payload, 0, $len ) = "" if !$peek;
106              
107 1837         3462 return $type, $num;
108             }
109              
110 1287         1290 method _pack ( $s )
  1287         1479  
  1287         1257  
111 1287     1287   1825 {
112 1287         2305 $_payload .= $s;
113             }
114              
115 1288         1348 method _unpack ( $num )
  1288         1379  
  1288         1236  
116 1288     1288   1838 {
117 1288 50       2297 length $_payload >= $num or croak "Can't pull $num bytes as there aren't enough";
118 1288         3187 return substr( $_payload, 0, $num, "" );
119             }
120              
121 16         27 method pack_bool ( $d )
  16         28  
  16         24  
122 16     16 0 46 {
123 16         56 TYPE_BOOL->pack_value( $self, $d );
124 16         38 return $self;
125             }
126              
127             method unpack_bool
128 16     16 0 37 {
129 16         45 return TYPE_BOOL->unpack_value( $self );
130             }
131              
132 473         550 method pack_int ( $d )
  473         559  
  473         512  
133 473     473 0 755 {
134 473         1245 TYPE_INT->pack_value( $self, $d );
135 469         1202 return $self;
136             }
137              
138             method unpack_int
139 472     472 0 1218 {
140 472         1082 return TYPE_INT->unpack_value( $self );
141             }
142              
143 294         343 method pack_str ( $d )
  294         392  
  294         290  
144 294     294 0 487 {
145 294         731 TYPE_STR->pack_value( $self, $d );
146 292         671 return $self;
147             }
148              
149             method unpack_str
150 294     294 0 458 {
151 294         644 return TYPE_STR->unpack_value( $self );
152             }
153              
154 161         175 method pack_record ( $rec, $struct = undef )
  161         190  
  161         196  
  161         168  
155 161     161 0 263 {
156 161 50 66     333 $struct ||= eval { Tangence::Struct->for_perlname( ref $rec ) } or
  21         84  
157             croak "No struct for " . ref $rec;
158              
159 161 100       360 $self->packmeta_struct( $struct ) unless $_stream->peer_hasstruct->{$struct->perlname};
160              
161 161         347 my @fields = $struct->fields;
162 161         336 $self->_pack_leader( DATA_RECORD, scalar @fields );
163 161         312 $self->pack_int( $_stream->peer_hasstruct->{$struct->perlname}->[1] );
164 161         272 foreach my $field ( @fields ) {
165 417         707 my $fieldname = $field->name;
166 417         663 $field->type->pack_value( $self, $rec->$fieldname );
167             }
168              
169 161         488 return $self;
170             }
171              
172 161         183 method unpack_record ( $struct = undef )
  161         194  
  161         168  
173 161     161 0 294 {
174 161         238 my ( $type, $num ) = $self->_unpack_leader();
175 161 50       303 $type == DATA_RECORD or croak "Expected to unpack a record but did not find one";
176              
177 161         270 my $structid = $self->unpack_int();
178 161         399 my $got_struct = $_stream->message_state->{id2struct}{$structid};
179 161 50       273 if( !$struct ) {
180 161         205 $struct = $got_struct;
181             }
182             else {
183 0 0       0 $struct->name eq $got_struct->name or
184             croak "Expected to unpack a ".$struct->name." but found ".$got_struct->name;
185             }
186              
187 161 50       357 $num == $struct->fields or croak "Expected ".$struct->name." to unpack from ".(scalar $struct->fields)." fields";
188              
189 161         186 my %values;
190 161         253 foreach my $field ( $struct->fields ) {
191 417         698 $values{$field->name} = $field->type->unpack_value( $self );
192             }
193              
194 161         353 return $struct->perlname->new( %values );
195             }
196              
197 20         28 method packmeta_construct ( $obj )
  20         29  
  20         30  
198 20     20 0 46 {
199 20         58 my $class = $obj->class;
200 20         59 my $id = $obj->id;
201              
202 20 100       106 $self->packmeta_class( $class ) unless $_stream->peer_hasclass->{$class->perlname};
203              
204 20         102 my $smashkeys = $class->smashkeys;
205              
206 20         54 $self->_pack_leader( DATA_META, DATAMETA_CONSTRUCT );
207 20         58 $self->pack_int( $id );
208 20         65 $self->pack_int( $_stream->peer_hasclass->{$class->perlname}->[2] );
209              
210 20 100       59 if( @$smashkeys ) {
211 11         73 my $smashdata = $obj->smash( $smashkeys );
212              
213 11         98 for my $prop ( @$smashkeys ) {
214 21         90 $_stream->_install_watch( $obj, $prop );
215             }
216              
217 11 50       97 if( $_stream->_ver_can_typed_smash ) {
218 11         52 $self->_pack_leader( DATA_LIST, scalar @$smashkeys );
219 11         39 foreach my $prop ( @$smashkeys ) {
220 21         57 $class->property( $prop )->overall_type->pack_value( $self, $smashdata->{$prop} );
221             }
222             }
223             else {
224 0         0 TYPE_LIST_ANY->pack_value( $self, [ map { $smashdata->{$_} } @$smashkeys ] );
  0         0  
225             }
226             }
227             else {
228 9         27 $self->_pack_leader( DATA_LIST, 0 );
229             }
230              
231 20         98 weaken( my $weakstream = $_stream );
232             $_stream->peer_hasobj->{$id} = $obj->subscribe_event(
233 2 50   2   23 destroy => sub { $weakstream->object_destroyed( @_ ) if $weakstream },
234 20         199 );
235             }
236              
237             method unpackmeta_construct
238 20     20 0 58 {
239 20         67 my $id = $self->unpack_int();
240 20         49 my $classid = $self->unpack_int();
241 20         53 my $class_perlname = $_stream->message_state->{id2class}{$classid};
242              
243 20         171 my ( $class, $smashkeys ) = @{ $_stream->peer_hasclass->{$class_perlname} };
  20         49  
244              
245 20         33 my $smasharr;
246 20 50       212 if( $_stream->_ver_can_typed_smash ) {
247 20         68 my ( $type, $num ) = $self->_unpack_leader;
248 20 50       62 $type == DATA_LIST or croak "Expected to unpack a LIST of smashed data";
249 20 50       52 $num == @$smashkeys or croak "Expected to unpack a LIST of " . ( scalar @$smashkeys ) . " elements";
250              
251 20         46 foreach my $prop ( @$smashkeys ) {
252 21         76 push @$smasharr, $class->property( $prop )->overall_type->unpack_value( $self );
253             }
254             }
255             else {
256 0         0 $smasharr = TYPE_LIST_ANY->unpack_value( $self );
257             }
258              
259 20         35 my $smashdata;
260 20         90 $smashdata->{$smashkeys->[$_]} = $smasharr->[$_] for 0 .. $#$smasharr;
261              
262 20         127 $_stream->make_proxy( $id, $class_perlname, $smashdata );
263             }
264              
265 20         35 method packmeta_class ( $class )
  20         31  
  20         30  
266 20     20 0 46 {
267 20         84 my @superclasses = grep { $_->name ne "Tangence.Object" } $class->direct_superclasses;
  1         3  
268              
269 20   33     59 $_stream->peer_hasclass->{$_->perlname} or $self->packmeta_class( $_ ) for @superclasses;
270              
271 20         63 $self->_pack_leader( DATA_META, DATAMETA_CLASS );
272              
273 20         76 my $smashkeys = $class->smashkeys;
274              
275 20         104 my $classid = ++$_stream->message_state->{next_classid};
276              
277 20         66 $self->pack_str( $class->name );
278 20         58 $self->pack_int( $classid );
279             my $classrec = Tangence::Struct::Class->new(
280             methods => {
281             pairmap {
282             $a => Tangence::Struct::Method->new(
283 28 100   28   116 arguments => [ map { $_->type->sig } $b->arguments ],
  28         90  
284             returns => ( $b->ret ? $b->ret->sig : "" ),
285             )
286 20         103 } %{ $class->direct_methods }
287             },
288             events => {
289             pairmap {
290             $a => Tangence::Struct::Event->new(
291 28     28   101 arguments => [ map { $_->type->sig } $b->arguments ],
  37         85  
292             )
293 20         90 } %{ $class->direct_events }
294             },
295             properties => {
296             pairmap {
297 83     83   207 $a => Tangence::Struct::Property->new(
298             dimension => $b->dimension,
299             type => $b->type->sig,
300             smashed => $b->smashed,
301             )
302 20         61 } %{ $class->direct_properties }
303             },
304 20         114 superclasses => [ map { $_->name } @superclasses ],
  1         4  
305             );
306 20         232 $self->pack_record( $classrec );
307              
308 20         58 TYPE_LIST_STR->pack_value( $self, $smashkeys );
309              
310 20         70 $_stream->peer_hasclass->{$class->perlname} = [ $class, $smashkeys, $classid ];
311             }
312              
313             method unpackmeta_class
314 20     20 0 43 {
315 20         58 my $name = $self->unpack_str();
316 20         445 my $classid = $self->unpack_int();
317 20         60 my $classrec = $self->unpack_record();
318              
319 20         192 my $class = Tangence::Meta::Class->new( name => $name );
320             $class->define(
321             methods => {
322             pairmap {
323             $a => Tangence::Meta::Method->new(
324             class => $class,
325             name => $a,
326             ret => $b->returns ? Tangence::Type->make_from_sig( $b->returns )
327             : undef,
328             arguments => [ map {
329 28         71 Tangence::Meta::Argument->new(
330             type => Tangence::Type->make_from_sig( $_ ),
331             )
332 28 100   28   107 } @{ $b->arguments } ],
  28         89  
333             )
334 20         62 } %{ $classrec->methods }
335             },
336              
337             events => {
338             pairmap {
339             $a => Tangence::Meta::Event->new(
340             class => $class,
341             name => $a,
342             arguments => [ map {
343 37         88 Tangence::Meta::Argument->new(
344             type => Tangence::Type->make_from_sig( $_ ),
345             )
346 28     28   66 } @{ $b->arguments } ],
  28         78  
347             )
348 20         72 } %{ $classrec->events }
349             },
350              
351             properties => {
352             pairmap {
353             # Need to use non-Meta:: Property so it can generate overall type
354             # using Tangence::Type instead of Tangence::Meta::Type
355 83     83   222 $a => Tangence::Property->new(
356             class => $class,
357             name => $a,
358             dimension => $b->dimension,
359             type => Tangence::Type->make_from_sig( $b->type ),
360             smashed => $b->smashed,
361             )
362 20         78 } %{ $classrec->properties }
363             },
364              
365 20         137 superclasses => do {
366             my @superclasses = map {
367 1         7 ( my $perlname = $_ ) =~ s/\./::/g;
368 1 50       4 $_stream->peer_hasclass->{$perlname}->[3] or croak "Unrecognised class $perlname";
369 20         39 } @{ $classrec->superclasses };
  20         65  
370              
371 20 100       137 @superclasses ? \@superclasses : [ Tangence::Class->for_name( "Tangence.Object" ) ]
372             },
373             );
374              
375 20         259 my $perlname = $class->perlname;
376              
377 20         62 my $smashkeys = TYPE_LIST_STR->unpack_value( $self );
378              
379 20         128 $_stream->peer_hasclass->{$perlname} = [ $class, $smashkeys, $classid, $class ];
380 20 50       58 if( defined $classid ) {
381 20         54 $_stream->message_state->{id2class}{$classid} = $perlname;
382             }
383             }
384              
385 2         3 method packmeta_struct ( $struct )
  2         2  
  2         3  
386 2     2 0 3 {
387 2         6 $self->_pack_leader( DATA_META, DATAMETA_STRUCT );
388              
389 2         7 my @fields = $struct->fields;
390              
391 2         8 my $structid = ++$_stream->message_state->{next_structid};
392 2         11 $self->pack_str( $struct->name );
393 2         7 $self->pack_int( $structid );
394 2         6 TYPE_LIST_STR->pack_value( $self, [ map { $_->name } @fields ] );
  4         8  
395 2         6 TYPE_LIST_STR->pack_value( $self, [ map { $_->type->sig } @fields ] );
  4         11  
396              
397 2         8 $_stream->peer_hasstruct->{$struct->perlname} = [ $struct, $structid ];
398             }
399              
400             method unpackmeta_struct
401 2     2 0 5 {
402 2         5 my $name = $self->unpack_str();
403 2         36 my $structid = $self->unpack_int();
404 2         7 my $names = TYPE_LIST_STR->unpack_value( $self );
405 2         5 my $types = TYPE_LIST_STR->unpack_value( $self );
406              
407 2         9 my $struct = Tangence::Struct->make( name => $name );
408 2 50       6 if( !$struct->defined ) {
409             $struct->define(
410             fields => [
411 0         0 map { $names->[$_] => $types->[$_] } 0 .. $#$names
  0         0  
412             ]
413             );
414             }
415              
416 2         7 $_stream->peer_hasstruct->{$struct->perlname} = [ $struct, $structid ];
417 2         5 $_stream->message_state->{id2struct}{$structid} = $struct;
418             }
419              
420 21         29 method pack_all_sametype ( $type, @d )
  21         36  
  21         49  
  21         25  
421 21     21 0 32 {
422 21         81 $type->pack_value( $self, $_ ) for @d;
423              
424 21         54 return $self;
425             }
426              
427 21         30 method unpack_all_sametype ( $type )
  21         30  
  21         26  
428 21     21 0 41 {
429 21         30 my @data;
430 21         69 push @data, $type->unpack_value( $self ) while length $_payload;
431              
432 21         115 return @data;
433             }
434              
435             =head1 AUTHOR
436              
437             Paul Evans
438              
439             =cut
440              
441             0x55AA;