File Coverage

blib/lib/Tangence/Message.pm
Criterion Covered Total %
statement 221 228 96.9
branch 25 36 69.4
condition 3 6 50.0
subroutine 40 40 100.0
pod 0 14 0.0
total 289 324 89.2


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-2024 -- leonerd@leonerd.org.uk
5              
6 12     12   221950 use v5.26;
  12         53  
7 12     12   83 use warnings;
  12         20  
  12         846  
8 12     12   1306 use Object::Pad 0.800;
  12         15465  
  12         803  
9              
10             package Tangence::Message 0.33;
11             class Tangence::Message;
12              
13 12     12   5769 use Carp;
  12         56  
  12         1012  
14              
15 12     12   433 use Tangence::Constants;
  12         28  
  12         3106  
16              
17 12     12   1479 use Tangence::Class;
  12         33  
  12         713  
18 12     12   89 use Tangence::Meta::Method;
  12         26  
  12         413  
19 12     12   80 use Tangence::Meta::Event;
  12         37  
  12         433  
20 12     12   72 use Tangence::Property;
  12         23  
  12         382  
21 12     12   71 use Tangence::Meta::Argument;
  12         34  
  12         334  
22 12     12   2042 use Tangence::Struct;
  12         31  
  12         535  
23 12     12   1675 use Tangence::Types;
  12         28  
  12         1317  
24              
25 12     12   2358 use Tangence::Object;
  12         31  
  12         859  
26              
27 12     12   82 use List::Util 1.29 qw( pairmap );
  12         308  
  12         1210  
28 12     12   102 use Scalar::Util qw( weaken blessed );
  12         43  
  12         136114  
29              
30             =head1 NAME
31              
32             C - contains a single C protocol message
33              
34             =head1 DESCRIPTION
35              
36             This module is a component of L or L. It
37             is not intended for end-user use directly.
38              
39             =cut
40              
41             # Normally we don't care about hash key order. But, when writing test scripts
42             # that will assert on the serialisation bytes, we do. Setting this to some
43             # true value will sort keys first
44             our $SORT_HASH_KEYS = 0;
45              
46 141     141 0 291 field $_stream :param :reader;
  141         429  
47 469     469 0 2760 field $_code :param :reader;
  469         2615  
48 417     417 0 33467 field $_payload :param :reader;
  417         1523  
49              
50 679         1181 sub BUILDARGS ( $class, $stream, $code, $payload = "" )
  679         1001  
  679         1179  
  679         1292  
51 679     679 0 369654 {
  679         993  
52 679         7261 return ( stream => $stream, code => $code, payload => $payload );
53             }
54              
55 1848     1848   2576 method _pack_leader ( $type, $num )
  1848         3721  
  1848         2731  
  1848         2757  
  1848         2505  
56             {
57 1848 100       3722 if( $num < 0x1f ) {
    50          
58 1839         6380 $_payload .= pack( "C", ( $type << 5 ) | $num );
59             }
60             elsif( $num < 0x80 ) {
61 9         56 $_payload .= pack( "CC", ( $type << 5 ) | 0x1f, $num );
62             }
63             else {
64 0         0 $_payload .= pack( "CN", ( $type << 5 ) | 0x1f, $num | 0x80000000 );
65             }
66             }
67              
68             method _peek_leader_type
69             {
70             while(1) {
71             length $_payload or croak "Ran out of bytes before finding a leader";
72              
73             my ( $typenum ) = unpack( "C", $_payload );
74             my $type = $typenum >> 5;
75              
76             return $type unless $type == DATA_META;
77              
78             substr( $_payload, 0, 1, "" );
79              
80             my $num = $typenum & 0x1f;
81             if( $num == DATAMETA_CONSTRUCT ) {
82             $self->unpackmeta_construct;
83             }
84             elsif( $num == DATAMETA_CLASS ) {
85             $self->unpackmeta_class;
86             }
87             elsif( $num == DATAMETA_STRUCT ) {
88             $self->unpackmeta_struct;
89             }
90             else {
91             die sprintf("TODO: Data stream meta-operation 0x%02x", $num);
92             }
93             }
94             }
95              
96 1839     1839   2681 method _unpack_leader ( $peek = 0 )
  1839         3363  
  1839         2709  
  1839         2352  
97             {
98 1839         4393 my $type = $self->_peek_leader_type;
99 1839         3241 my ( $typenum ) = unpack( "C", $_payload );
100              
101 1839         3083 my $num = $typenum & 0x1f;
102              
103 1839         2535 my $len = 1;
104 1839 100       3732 if( $num == 0x1f ) {
105 9         24 ( $num ) = unpack( "x C", $_payload );
106              
107 9 50       26 if( $num < 0x80 ) {
108 9         18 $len = 2;
109             }
110             else {
111 0         0 ( $num ) = unpack( "x N", $_payload );
112 0         0 $num &= 0x7fffffff;
113 0         0 $len = 5;
114             }
115             }
116              
117 1839 100       4518 substr( $_payload, 0, $len ) = "" if !$peek;
118              
119 1839         4684 return $type, $num;
120             }
121              
122 1287     1287   1926 method _pack ( $s )
  1287         2668  
  1287         2121  
  1287         1676  
123             {
124 1287         3427 $_payload .= $s;
125             }
126              
127 1288     1288   1851 method _unpack ( $num )
  1288         2541  
  1288         1934  
  1288         1658  
128             {
129 1288 50       2786 length $_payload >= $num or croak "Can't pull $num bytes as there aren't enough";
130 1288         4200 return substr( $_payload, 0, $num, "" );
131             }
132              
133 16     16 0 48 method pack_bool ( $d )
  16         68  
  16         33  
  16         27  
134             {
135 16         113 TYPE_BOOL->pack_value( $self, $d );
136 16         79 return $self;
137             }
138              
139             method unpack_bool
140             {
141             return TYPE_BOOL->unpack_value( $self );
142             }
143              
144 473     473 0 915 method pack_int ( $d )
  473         914  
  473         864  
  473         721  
145             {
146 473         1968 TYPE_INT->pack_value( $self, $d );
147 469         1605 return $self;
148             }
149              
150             method unpack_int
151             {
152             return TYPE_INT->unpack_value( $self );
153             }
154              
155 294     294 0 530 method pack_str ( $d )
  294         567  
  294         563  
  294         458  
156             {
157 294         1146 TYPE_STR->pack_value( $self, $d );
158 292         1067 return $self;
159             }
160              
161             method unpack_str
162             {
163             return TYPE_STR->unpack_value( $self );
164             }
165              
166 161     161 0 279 method pack_record ( $rec, $struct = undef )
  161         371  
  161         253  
  161         255  
  161         231  
167             {
168 161 50 66     528 $struct ||= eval { Tangence::Struct->for_perlname( ref $rec ) } or
  21         136  
169             croak "No struct for " . ref $rec;
170              
171 161 100       745 $self->packmeta_struct( $struct ) unless $_stream->peer_hasstruct->{$struct->perlname};
172              
173 161         706 my @fields = $struct->fields;
174 161         502 $self->_pack_leader( DATA_RECORD, scalar @fields );
175 161         432 $self->pack_int( $_stream->peer_hasstruct->{$struct->perlname}->[1] );
176 161         389 foreach my $field ( @fields ) {
177 417         1202 my $fieldname = $field->name;
178 417         962 $field->type->pack_value( $self, $rec->$fieldname );
179             }
180              
181 161         798 return $self;
182             }
183              
184 161     161 0 316 method unpack_record ( $struct = undef )
  161         343  
  161         251  
  161         304  
185             {
186 161         433 my ( $type, $num ) = $self->_unpack_leader();
187 161 50       392 $type == DATA_RECORD or croak "Expected to unpack a record but did not find one";
188              
189 161         453 my $structid = $self->unpack_int();
190 161         612 my $got_struct = $_stream->message_state->{id2struct}{$structid};
191 161 50       380 if( !$struct ) {
192 161         259 $struct = $got_struct;
193             }
194             else {
195 0 0       0 $struct->name eq $got_struct->name or
196             croak "Expected to unpack a ".$struct->name." but found ".$got_struct->name;
197             }
198              
199 161 50       780 $num == $struct->fields or croak "Expected ".$struct->name." to unpack from ".(scalar $struct->fields)." fields";
200              
201 161         245 my %values;
202 161         442 foreach my $field ( $struct->fields ) {
203 417         972 $values{$field->name} = $field->type->unpack_value( $self );
204             }
205              
206 161         708 return $struct->perlname->new( %values );
207             }
208              
209 20     20 0 49 method packmeta_construct ( $obj )
  20         67  
  20         38  
  20         36  
210             {
211 20         84 my $class = $obj->class;
212 20         68 my $id = $obj->id;
213              
214 20 100       114 $self->packmeta_class( $class ) unless $_stream->peer_hasclass->{$class->perlname};
215              
216 20         145 my $smashkeys = $class->smashkeys;
217              
218 20         88 $self->_pack_leader( DATA_META, DATAMETA_CONSTRUCT );
219 20         74 $self->pack_int( $id );
220 20         78 $self->pack_int( $_stream->peer_hasclass->{$class->perlname}->[2] );
221              
222 20 100       74 if( @$smashkeys ) {
223 11         121 my $smashdata = $obj->smash( $smashkeys );
224              
225 11         133 for my $prop ( @$smashkeys ) {
226 21         139 $_stream->_install_watch( $obj, $prop );
227             }
228              
229 11 50       116 if( $_stream->_ver_can_typed_smash ) {
230 11         99 $self->_pack_leader( DATA_LIST, scalar @$smashkeys );
231 11         34 foreach my $prop ( @$smashkeys ) {
232 21         92 $class->property( $prop )->overall_type->pack_value( $self, $smashdata->{$prop} );
233             }
234             }
235             else {
236 0         0 TYPE_LIST_ANY->pack_value( $self, [ map { $smashdata->{$_} } @$smashkeys ] );
  0         0  
237             }
238             }
239             else {
240 9         38 $self->_pack_leader( DATA_LIST, 0 );
241             }
242              
243 20         69 weaken( my $weakstream = $_stream );
244             $_stream->peer_hasobj->{$id} = $obj->subscribe_event(
245 2 50   2   29 destroy => sub { $weakstream->object_destroyed( @_ ) if $weakstream },
246 20         277 );
247             }
248              
249             method unpackmeta_construct
250             {
251             my $id = $self->unpack_int();
252             my $classid = $self->unpack_int();
253             my $class_perlname = $_stream->message_state->{id2class}{$classid};
254              
255             my ( $class, $smashkeys ) = @{ $_stream->peer_hasclass->{$class_perlname} };
256              
257             my $smasharr;
258             if( $_stream->_ver_can_typed_smash ) {
259             my ( $type, $num ) = $self->_unpack_leader;
260             $type == DATA_LIST or croak "Expected to unpack a LIST of smashed data";
261             $num == @$smashkeys or croak "Expected to unpack a LIST of " . ( scalar @$smashkeys ) . " elements";
262              
263             foreach my $prop ( @$smashkeys ) {
264             push @$smasharr, $class->property( $prop )->overall_type->unpack_value( $self );
265             }
266             }
267             else {
268             $smasharr = TYPE_LIST_ANY->unpack_value( $self );
269             }
270              
271             my $smashdata;
272             $smashdata->{$smashkeys->[$_]} = $smasharr->[$_] for 0 .. $#$smasharr;
273              
274             $_stream->make_proxy( $id, $class_perlname, $smashdata );
275             }
276              
277 20     20 0 40 method packmeta_class ( $class )
  20         58  
  20         43  
  20         34  
278             {
279 20         180 my @superclasses = grep { $_->name ne "Tangence.Object" } $class->direct_superclasses;
  1         5  
280              
281 20   33     118 $_stream->peer_hasclass->{$_->perlname} or $self->packmeta_class( $_ ) for @superclasses;
282              
283 20         91 $self->_pack_leader( DATA_META, DATAMETA_CLASS );
284              
285 20         130 my $smashkeys = $class->smashkeys;
286              
287 20         128 my $classid = ++$_stream->message_state->{next_classid};
288              
289 20         111 $self->pack_str( $class->name );
290 20         91 $self->pack_int( $classid );
291             my $classrec = Tangence::Struct::Class->new(
292             methods => {
293             pairmap {
294             $a => Tangence::Struct::Method->new(
295 28 100   28   2137 arguments => [ map { $_->type->sig } $b->arguments ],
  28         2269  
296             returns => ( $b->ret ? $b->ret->sig : "" ),
297             )
298 20         165 } %{ $class->direct_methods }
299             },
300             events => {
301             pairmap {
302             $a => Tangence::Struct::Event->new(
303 28     28   196 arguments => [ map { $_->type->sig } $b->arguments ],
  37         157  
304             )
305 20         134 } %{ $class->direct_events }
306             },
307             properties => {
308             pairmap {
309 83     83   261 $a => Tangence::Struct::Property->new(
310             dimension => $b->dimension,
311             type => $b->type->sig,
312             smashed => $b->smashed,
313             )
314 20         115 } %{ $class->direct_properties }
315             },
316 20         140 superclasses => [ map { $_->name } @superclasses ],
  1         5  
317             );
318 20         354 $self->pack_record( $classrec );
319              
320 20         107 TYPE_LIST_STR->pack_value( $self, $smashkeys );
321              
322 20         148 $_stream->peer_hasclass->{$class->perlname} = [ $class, $smashkeys, $classid ];
323             }
324              
325             method unpackmeta_class
326             {
327             my $name = $self->unpack_str();
328             my $classid = $self->unpack_int();
329             my $classrec = $self->unpack_record();
330              
331             my $class = Tangence::Meta::Class->new( name => $name );
332             $class->define(
333             methods => {
334             pairmap {
335             $a => Tangence::Meta::Method->new(
336             class => $class,
337             name => $a,
338             ret => $b->returns ? Tangence::Type->make_from_sig( $b->returns )
339             : undef,
340             arguments => [ map {
341 28         81 Tangence::Meta::Argument->new(
342             type => Tangence::Type->make_from_sig( $_ ),
343             )
344 28 100   28   114 } @{ $b->arguments } ],
  28         115  
345             )
346             } %{ $classrec->methods }
347             },
348              
349             events => {
350             pairmap {
351             $a => Tangence::Meta::Event->new(
352             class => $class,
353             name => $a,
354             arguments => [ map {
355 37         353 Tangence::Meta::Argument->new(
356             type => Tangence::Type->make_from_sig( $_ ),
357             )
358 28     28   73 } @{ $b->arguments } ],
  28         100  
359             )
360             } %{ $classrec->events }
361             },
362              
363             properties => {
364             pairmap {
365             # Need to use non-Meta:: Property so it can generate overall type
366             # using Tangence::Type instead of Tangence::Meta::Type
367 83     83   282 $a => Tangence::Property->new(
368             class => $class,
369             name => $a,
370             dimension => $b->dimension,
371             type => Tangence::Type->make_from_sig( $b->type ),
372             smashed => $b->smashed,
373             )
374             } %{ $classrec->properties }
375             },
376              
377             superclasses => do {
378             my @superclasses = map {
379             ( my $perlname = $_ ) =~ s/\./::/g;
380             $_stream->peer_hasclass->{$perlname}->[3] or croak "Unrecognised class $perlname";
381             } @{ $classrec->superclasses };
382              
383             @superclasses ? \@superclasses : [ Tangence::Class->for_name( "Tangence.Object" ) ]
384             },
385             );
386              
387             my $perlname = $class->perlname;
388              
389             my $smashkeys = TYPE_LIST_STR->unpack_value( $self );
390              
391             $_stream->peer_hasclass->{$perlname} = [ $class, $smashkeys, $classid, $class ];
392             if( defined $classid ) {
393             $_stream->message_state->{id2class}{$classid} = $perlname;
394             }
395             }
396              
397 2     2 0 3 method packmeta_struct ( $struct )
  2         7  
  2         3  
  2         4  
398             {
399 2         8 $self->_pack_leader( DATA_META, DATAMETA_STRUCT );
400              
401 2         21 my @fields = $struct->fields;
402              
403 2         17 my $structid = ++$_stream->message_state->{next_structid};
404 2         16 $self->pack_str( $struct->name );
405 2         8 $self->pack_int( $structid );
406 2         6 TYPE_LIST_STR->pack_value( $self, [ map { $_->name } @fields ] );
  4         13  
407 2         6 TYPE_LIST_STR->pack_value( $self, [ map { $_->type->sig } @fields ] );
  4         11  
408              
409 2         10 $_stream->peer_hasstruct->{$struct->perlname} = [ $struct, $structid ];
410             }
411              
412             method unpackmeta_struct
413             {
414             my $name = $self->unpack_str();
415             my $structid = $self->unpack_int();
416             my $names = TYPE_LIST_STR->unpack_value( $self );
417             my $types = TYPE_LIST_STR->unpack_value( $self );
418              
419             my $struct = Tangence::Struct->make( name => $name );
420             if( !$struct->defined ) {
421             $struct->define(
422             fields => [
423             map { $names->[$_] => $types->[$_] } 0 .. $#$names
424             ]
425             );
426             }
427              
428             $_stream->peer_hasstruct->{$struct->perlname} = [ $struct, $structid ];
429             $_stream->message_state->{id2struct}{$structid} = $struct;
430             }
431              
432 21     21 0 43 method pack_all_sametype ( $type, @d )
  21         62  
  21         38  
  21         68  
  21         29  
433             {
434 21         113 $type->pack_value( $self, $_ ) for @d;
435              
436 21         132 return $self;
437             }
438              
439 21     21 0 46 method unpack_all_sametype ( $type )
  21         73  
  21         38  
  21         33  
440             {
441 21         42 my @data;
442 21         121 push @data, $type->unpack_value( $self ) while length $_payload;
443              
444 21         213 return @data;
445             }
446              
447             =head1 AUTHOR
448              
449             Paul Evans
450              
451             =cut
452              
453             0x55AA;