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-2022 -- leonerd@leonerd.org.uk
5              
6 12     12   73275 use v5.26;
  12         56  
7 12     12   1163 use Object::Pad 0.66;
  12         15816  
  12         68  
8              
9             package Tangence::Message 0.30;
10             class Tangence::Message;
11              
12 12     12   4418 use Carp;
  12         29  
  12         788  
13              
14 12     12   481 use Tangence::Constants;
  12         22  
  12         2198  
15              
16 12     12   1228 use Tangence::Class;
  12         44  
  12         469  
17 12     12   74 use Tangence::Meta::Method;
  12         21  
  12         373  
18 12     12   69 use Tangence::Meta::Event;
  12         33  
  12         318  
19 12     12   77 use Tangence::Property;
  12         23  
  12         353  
20 12     12   99 use Tangence::Meta::Argument;
  12         26  
  12         353  
21 12     12   1629 use Tangence::Struct;
  12         38  
  12         430  
22 12     12   1276 use Tangence::Types;
  12         30  
  12         929  
23              
24 12     12   1843 use Tangence::Object;
  12         39  
  12         668  
25              
26 12     12   81 use List::Util 1.29 qw( pairmap );
  12         216  
  12         1483  
27 12     12   234 use Scalar::Util qw( weaken blessed );
  12         44  
  12         84157  
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 226 field $_stream :param :reader;
  141         376  
35 469     469 0 2339 field $_code :param :reader;
  469         2069  
36 415     415 0 34171 field $_payload :param :reader;
  415         1383  
37              
38 678         1028 sub BUILDARGS ( $class, $stream, $code, $payload = "" )
  678         866  
  678         984  
  678         1005  
39 678     678 0 17551 {
  678         821  
40 678         4402 return ( stream => $stream, code => $code, payload => $payload );
41             }
42              
43 1847         2261 method _pack_leader ( $type, $num )
  1847         2338  
  1847         2294  
  1847         2121  
44 1847     1847   3168 {
45 1847 100       2982 if( $num < 0x1f ) {
    50          
46 1838         5116 $_payload .= pack( "C", ( $type << 5 ) | $num );
47             }
48             elsif( $num < 0x80 ) {
49 9         40 $_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   2916 {
58 2045         2875 while(1) {
59 2087 50       3837 length $_payload or croak "Ran out of bytes before finding a leader";
60              
61 2087         4084 my ( $typenum ) = unpack( "C", $_payload );
62 2087         3203 my $type = $typenum >> 5;
63              
64 2087 100       4706 return $type unless $type == DATA_META;
65              
66 42         88 substr( $_payload, 0, 1, "" );
67              
68 42         77 my $num = $typenum & 0x1f;
69 42 100       136 if( $num == DATAMETA_CONSTRUCT ) {
    100          
    50          
70 20         82 $self->unpackmeta_construct;
71             }
72             elsif( $num == DATAMETA_CLASS ) {
73 20         118 $self->unpackmeta_class;
74             }
75             elsif( $num == DATAMETA_STRUCT ) {
76 2         8 $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         2266 method _unpack_leader ( $peek = 0 )
  1837         2404  
  1837         2175  
85 1837     1837   2904 {
86 1837         2955 my $type = $self->_peek_leader_type;
87 1837         3076 my ( $typenum ) = unpack( "C", $_payload );
88              
89 1837         2617 my $num = $typenum & 0x1f;
90              
91 1837         2223 my $len = 1;
92 1837 100       3174 if( $num == 0x1f ) {
93 9         22 ( $num ) = unpack( "x C", $_payload );
94              
95 9 50       27 if( $num < 0x80 ) {
96 9         16 $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       3781 substr( $_payload, 0, $len ) = "" if !$peek;
106              
107 1837         4305 return $type, $num;
108             }
109              
110 1287         1584 method _pack ( $s )
  1287         1754  
  1287         1510  
111 1287     1287   2288 {
112 1287         2860 $_payload .= $s;
113             }
114              
115 1288         1605 method _unpack ( $num )
  1288         1630  
  1288         1516  
116 1288     1288   2253 {
117 1288 50       2345 length $_payload >= $num or croak "Can't pull $num bytes as there aren't enough";
118 1288         3806 return substr( $_payload, 0, $num, "" );
119             }
120              
121 16         41 method pack_bool ( $d )
  16         53  
  16         33  
122 16     16 0 54 {
123 16         87 TYPE_BOOL->pack_value( $self, $d );
124 16         54 return $self;
125             }
126              
127             method unpack_bool
128 16     16 0 40 {
129 16         68 return TYPE_BOOL->unpack_value( $self );
130             }
131              
132 473         621 method pack_int ( $d )
  473         680  
  473         580  
133 473     473 0 884 {
134 473         1421 TYPE_INT->pack_value( $self, $d );
135 469         1392 return $self;
136             }
137              
138             method unpack_int
139 472     472 0 1400 {
140 472         1242 return TYPE_INT->unpack_value( $self );
141             }
142              
143 294         431 method pack_str ( $d )
  294         438  
  294         431  
144 294     294 0 558 {
145 294         832 TYPE_STR->pack_value( $self, $d );
146 292         781 return $self;
147             }
148              
149             method unpack_str
150 294     294 0 579 {
151 294         773 return TYPE_STR->unpack_value( $self );
152             }
153              
154 161         215 method pack_record ( $rec, $struct = undef )
  161         228  
  161         250  
  161         192  
155 161     161 0 340 {
156 161 50 66     441 $struct ||= eval { Tangence::Struct->for_perlname( ref $rec ) } or
  21         97  
157             croak "No struct for " . ref $rec;
158              
159 161 100       440 $self->packmeta_struct( $struct ) unless $_stream->peer_hasstruct->{$struct->perlname};
160              
161 161         414 my @fields = $struct->fields;
162 161         417 $self->_pack_leader( DATA_RECORD, scalar @fields );
163 161         376 $self->pack_int( $_stream->peer_hasstruct->{$struct->perlname}->[1] );
164 161         322 foreach my $field ( @fields ) {
165 417         883 my $fieldname = $field->name;
166 417         883 $field->type->pack_value( $self, $rec->$fieldname );
167             }
168              
169 161         588 return $self;
170             }
171              
172 161         215 method unpack_record ( $struct = undef )
  161         241  
  161         217  
173 161     161 0 298 {
174 161         267 my ( $type, $num ) = $self->_unpack_leader();
175 161 50       314 $type == DATA_RECORD or croak "Expected to unpack a record but did not find one";
176              
177 161         333 my $structid = $self->unpack_int();
178 161         449 my $got_struct = $_stream->message_state->{id2struct}{$structid};
179 161 50       325 if( !$struct ) {
180 161         235 $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       408 $num == $struct->fields or croak "Expected ".$struct->name." to unpack from ".(scalar $struct->fields)." fields";
188              
189 161         230 my %values;
190 161         331 foreach my $field ( $struct->fields ) {
191 417         870 $values{$field->name} = $field->type->unpack_value( $self );
192             }
193              
194 161         462 return $struct->perlname->new( %values );
195             }
196              
197 20         43 method packmeta_construct ( $obj )
  20         37  
  20         48  
198 20     20 0 55 {
199 20         85 my $class = $obj->class;
200 20         92 my $id = $obj->id;
201              
202 20 100       125 $self->packmeta_class( $class ) unless $_stream->peer_hasclass->{$class->perlname};
203              
204 20         83 my $smashkeys = $class->smashkeys;
205              
206 20         72 $self->_pack_leader( DATA_META, DATAMETA_CONSTRUCT );
207 20         64 $self->pack_int( $id );
208 20         93 $self->pack_int( $_stream->peer_hasclass->{$class->perlname}->[2] );
209              
210 20 100       90 if( @$smashkeys ) {
211 11         91 my $smashdata = $obj->smash( $smashkeys );
212              
213 11         116 for my $prop ( @$smashkeys ) {
214 21         101 $_stream->_install_watch( $obj, $prop );
215             }
216              
217 11 50       94 if( $_stream->_ver_can_typed_smash ) {
218 11         80 $self->_pack_leader( DATA_LIST, scalar @$smashkeys );
219 11         46 foreach my $prop ( @$smashkeys ) {
220 21         72 $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         43 $self->_pack_leader( DATA_LIST, 0 );
229             }
230              
231 20         117 weaken( my $weakstream = $_stream );
232             $_stream->peer_hasobj->{$id} = $obj->subscribe_event(
233 2 50   2   17 destroy => sub { $weakstream->object_destroyed( @_ ) if $weakstream },
234 20         199 );
235             }
236              
237             method unpackmeta_construct
238 20     20 0 65 {
239 20         225 my $id = $self->unpack_int();
240 20         78 my $classid = $self->unpack_int();
241 20         75 my $class_perlname = $_stream->message_state->{id2class}{$classid};
242              
243 20         47 my ( $class, $smashkeys ) = @{ $_stream->peer_hasclass->{$class_perlname} };
  20         75  
244              
245 20         40 my $smasharr;
246 20 50       91 if( $_stream->_ver_can_typed_smash ) {
247 20         64 my ( $type, $num ) = $self->_unpack_leader;
248 20 50       94 $type == DATA_LIST or croak "Expected to unpack a LIST of smashed data";
249 20 50       64 $num == @$smashkeys or croak "Expected to unpack a LIST of " . ( scalar @$smashkeys ) . " elements";
250              
251 20         56 foreach my $prop ( @$smashkeys ) {
252 21         84 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         43 my $smashdata;
260 20         137 $smashdata->{$smashkeys->[$_]} = $smasharr->[$_] for 0 .. $#$smasharr;
261              
262 20         146 $_stream->make_proxy( $id, $class_perlname, $smashdata );
263             }
264              
265 20         45 method packmeta_class ( $class )
  20         38  
  20         30  
266 20     20 0 53 {
267 20         96 my @superclasses = grep { $_->name ne "Tangence.Object" } $class->direct_superclasses;
  1         4  
268              
269 20   33     69 $_stream->peer_hasclass->{$_->perlname} or $self->packmeta_class( $_ ) for @superclasses;
270              
271 20         81 $self->_pack_leader( DATA_META, DATAMETA_CLASS );
272              
273 20         68 my $smashkeys = $class->smashkeys;
274              
275 20         100 my $classid = ++$_stream->message_state->{next_classid};
276              
277 20         76 $self->pack_str( $class->name );
278 20         93 $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   124 arguments => [ map { $_->type->sig } $b->arguments ],
  28         100  
284             returns => ( $b->ret ? $b->ret->sig : "" ),
285             )
286 20         139 } %{ $class->direct_methods }
287             },
288             events => {
289             pairmap {
290             $a => Tangence::Struct::Event->new(
291 28     28   133 arguments => [ map { $_->type->sig } $b->arguments ],
  37         106  
292             )
293 20         83 } %{ $class->direct_events }
294             },
295             properties => {
296             pairmap {
297 83     83   244 $a => Tangence::Struct::Property->new(
298             dimension => $b->dimension,
299             type => $b->type->sig,
300             smashed => $b->smashed,
301             )
302 20         80 } %{ $class->direct_properties }
303             },
304 20         179 superclasses => [ map { $_->name } @superclasses ],
  1         4  
305             );
306 20         267 $self->pack_record( $classrec );
307              
308 20         73 TYPE_LIST_STR->pack_value( $self, $smashkeys );
309              
310 20         86 $_stream->peer_hasclass->{$class->perlname} = [ $class, $smashkeys, $classid ];
311             }
312              
313             method unpackmeta_class
314 20     20 0 52 {
315 20         88 my $name = $self->unpack_str();
316 20         503 my $classid = $self->unpack_int();
317 20         67 my $classrec = $self->unpack_record();
318              
319 20         168 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         82 Tangence::Meta::Argument->new(
330             type => Tangence::Type->make_from_sig( $_ ),
331             )
332 28 100   28   131 } @{ $b->arguments } ],
  28         88  
333             )
334 20         131 } %{ $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         125 Tangence::Meta::Argument->new(
344             type => Tangence::Type->make_from_sig( $_ ),
345             )
346 28     28   72 } @{ $b->arguments } ],
  28         90  
347             )
348 20         80 } %{ $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   260 $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         76 } %{ $classrec->properties }
363             },
364              
365 20         152 superclasses => do {
366             my @superclasses = map {
367 1         7 ( my $perlname = $_ ) =~ s/\./::/g;
368 1 50       183 $_stream->peer_hasclass->{$perlname}->[3] or croak "Unrecognised class $perlname";
369 20         80 } @{ $classrec->superclasses };
  20         64  
370              
371 20 100       120 @superclasses ? \@superclasses : [ Tangence::Class->for_name( "Tangence.Object" ) ]
372             },
373             );
374              
375 20         273 my $perlname = $class->perlname;
376              
377 20         78 my $smashkeys = TYPE_LIST_STR->unpack_value( $self );
378              
379 20         135 $_stream->peer_hasclass->{$perlname} = [ $class, $smashkeys, $classid, $class ];
380 20 50       70 if( defined $classid ) {
381 20         87 $_stream->message_state->{id2class}{$classid} = $perlname;
382             }
383             }
384              
385 2         4 method packmeta_struct ( $struct )
  2         4  
  2         5  
386 2     2 0 5 {
387 2         5 $self->_pack_leader( DATA_META, DATAMETA_STRUCT );
388              
389 2         9 my @fields = $struct->fields;
390              
391 2         12 my $structid = ++$_stream->message_state->{next_structid};
392 2         17 $self->pack_str( $struct->name );
393 2         11 $self->pack_int( $structid );
394 2         9 TYPE_LIST_STR->pack_value( $self, [ map { $_->name } @fields ] );
  4         15  
395 2         8 TYPE_LIST_STR->pack_value( $self, [ map { $_->type->sig } @fields ] );
  4         24  
396              
397 2         10 $_stream->peer_hasstruct->{$struct->perlname} = [ $struct, $structid ];
398             }
399              
400             method unpackmeta_struct
401 2     2 0 5 {
402 2         6 my $name = $self->unpack_str();
403 2         44 my $structid = $self->unpack_int();
404 2         13 my $names = TYPE_LIST_STR->unpack_value( $self );
405 2         5 my $types = TYPE_LIST_STR->unpack_value( $self );
406              
407 2         13 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         19 $_stream->peer_hasstruct->{$struct->perlname} = [ $struct, $structid ];
417 2         7 $_stream->message_state->{id2struct}{$structid} = $struct;
418             }
419              
420 21         36 method pack_all_sametype ( $type, @d )
  21         36  
  21         36  
  21         38  
421 21     21 0 51 {
422 21         76 $type->pack_value( $self, $_ ) for @d;
423              
424 21         75 return $self;
425             }
426              
427 21         31 method unpack_all_sametype ( $type )
  21         34  
  21         27  
428 21     21 0 51 {
429 21         35 my @data;
430 21         80 push @data, $type->unpack_value( $self ) while length $_payload;
431              
432 21         143 return @data;
433             }
434              
435             =head1 AUTHOR
436              
437             Paul Evans
438              
439             =cut
440              
441             0x55AA;