File Coverage

lib/Net/EGTS/Packet.pm
Criterion Covered Total %
statement 144 162 88.8
branch 32 52 61.5
condition 9 18 50.0
subroutine 24 24 100.0
pod 4 6 66.6
total 213 262 81.3


line stmt bran cond sub pod time code
1 9     9   205581 use utf8;
  9         41  
  9         44  
2              
3             package Net::EGTS::Packet;
4 9     9   1659 use namespace::autoclean;
  9         51432  
  9         29  
5 9     9   1736 use Mouse;
  9         81040  
  9         32  
6              
7 9     9   2761 use Carp;
  9         12  
  9         399  
8 9     9   1675 use List::MoreUtils qw(natatime any);
  9         36349  
  9         47  
9 9     9   9356 use Module::Load qw(load);
  9         7701  
  9         42  
10              
11 9     9   1506 use Net::EGTS::Util qw(crc8 crc16 usize dumper_bitstring);
  9         16  
  9         481  
12 9     9   1107 use Net::EGTS::Types;
  9         324  
  9         169  
13 9     9   1094 use Net::EGTS::Codes;
  9         247  
  9         4074  
14              
15             require Net::EGTS::Record;
16              
17             # Global packet identifier
18             our $PID = 0;
19              
20             # Packet types and classes
21             our %TYPES = (
22             EGTS_PT_RESPONSE, 'Net::EGTS::Packet::Response',
23             EGTS_PT_APPDATA, 'Net::EGTS::Packet::Appdata',
24             EGTS_PT_SIGNED_APPDATA, 'Net::EGTS::Packet::SignedAppdata',
25             );
26              
27             # Protocol Version
28             has PRV => is => 'rw', isa => 'BYTE', default => 0x01;
29             # Security Key ID
30             has SKID => is => 'rw', isa => 'BYTE', default => 0;
31              
32             # Flags:
33             # Prefix
34             has PRF => is => 'rw', isa => 'BIT2', default => 0b00;
35             # Route
36             has RTE => is => 'rw', isa => 'BIT1', default => 0b0;
37             # Encryption Algorithm
38             has ENA => is => 'rw', isa => 'BIT2', default => 0b00;
39             # Compressed
40             has CMP => is => 'rw', isa => 'BIT1', default => 0b0;
41             # Priority
42             has PRIORITY => is => 'rw', isa => 'BIT2', default => 0b00;
43              
44             # Header Length
45             has HL =>
46             is => 'rw',
47             isa => 'BYTE',
48             lazy => 1,
49             builder => sub {
50             my ($self) = @_;
51             my $length = 11;
52             $length += 2 if defined $self->PRA;
53             $length += 2 if defined $self->RCA;
54             $length += 1 if defined $self->TTL;
55             return $length;
56             },
57             ;
58             # Header Encoding
59             has HE => is => 'rw', isa => 'BYTE', default => 0x0;
60             # Frame Data Length
61             has FDL =>
62             is => 'rw',
63             isa => 'USHORT',
64             lazy => 1,
65             builder => sub {
66             my ($self) = @_;
67 9     9   55 use bytes;
  9         11  
  9         38  
68             return 0 unless defined $self->SFRD;
69             return 0 unless length $self->SFRD;
70             return length $self->SFRD;
71             },
72             ;
73             # Packet Identifier
74             has PID =>
75             is => 'rw',
76             isa => 'USHORT',
77             lazy => 1,
78             builder => sub {
79             my $pid = $PID;
80             $PID = 0 unless ++$PID >= 0 && $PID <= 65535;
81             return $pid;
82             }
83             ;
84             # Packet Type
85             has PT => is => 'rw', isa => 'BYTE';
86              
87             # Optional (set if RTE enabled):
88             # Peer Address
89             has PRA => is => 'rw', isa => 'Maybe[USHORT]';
90             # Recipient Address
91             has RCA => is => 'rw', isa => 'Maybe[USHORT]';
92             # Time To Live
93             has TTL => is => 'rw', isa => 'Maybe[BYTE]';
94              
95             # Header Check Sum
96             has HCS =>
97             is => 'rw',
98             isa => 'BYTE',
99             lazy => 1,
100             builder => sub {
101             my ($self) = @_;
102 9     9   1511 use bytes;
  9         13  
  9         22  
103             my $length = $self->HL - 1; # HL - HCS
104             die 'Binary too short to get CRC8' if $length > length $self->bin;
105             return crc8( substr( $self->bin, 0 => $length ) );
106             },
107             ;
108              
109             # Service Frame Data
110             has SFRD =>
111             is => 'rw',
112             isa => 'Maybe[BINARY]',
113             default => '',
114             trigger => sub {
115             my ($self, $value, $old) = @_;
116             die 'Service Frame Data too long'
117             if defined($value) && length($value) > 65517;
118             }
119             ;
120             # Service Frame Data Check Sum
121             has SFRCS =>
122             is => 'rw',
123             isa => 'Maybe[USHORT]',
124             lazy => 1,
125             builder => sub {
126             my ($self) = @_;
127 9     9   1225 use bytes;
  9         14  
  9         21  
128             die 'Binary too short to get CRC16' if $self->FDL > length $self->SFRD;
129             return undef unless defined $self->SFRD;
130             return undef unless length $self->SFRD;
131             return crc16( $self->SFRD );
132             }
133             ;
134              
135             # Private:
136             # Packet binary
137             has bin => is => 'rw', isa => 'Str', default => '';
138              
139             # Array of decoded records
140             has records =>
141             is => 'rw',
142             isa => 'ArrayRef[Net::EGTS::Record]',
143             lazy => 1,
144             builder => sub {
145             my ($self) = @_;
146             return [] unless defined $self->SDR;
147             return [] unless length $self->SDR;
148             return Net::EGTS::Record->decode_all( $self->SDR );
149             },
150             ;
151              
152             #around BUILDARGS => sub {
153             # my $orig = shift;
154             # my $class = shift;
155             #
156             # # simple scalar decoding support
157             # my $bin = @_ % 2 ? shift : undef;
158             # my %opts = @_;
159             #
160             # return $class->$orig(
161             # bin => $bin,
162             # %opts
163             # ) if $bin;
164             # return $class->$orig( %opts );
165             #};
166             #sub BUILD {
167             # my $self = shift;
168             # my $args = shift;
169             #
170             # $self->decode( \$self->bin ) if length $self->bin;
171             # use Data::Dumper;
172             # warn Dumper($self);
173             #}
174              
175             # Store binary and count how mutch more bytes need
176             sub take {
177 128     128 0 158 my ($self, $bin, $mask, $length) = @_;
178 9     9   1400 use bytes;
  9         18  
  9         45  
179              
180 128   66     252 $length //= usize($mask);
181 128 50       185 confess "Can`t get chunk of length $length" if $length > length $$bin;
182              
183 128         155 my $chunk = substr $$bin, 0 => $length, '';
184 128         328 $self->bin( $self->bin . $chunk );
185              
186 128         420 return unpack $mask => $chunk;
187             }
188              
189             # Helper to get portion of data
190             sub nip {
191 21     21 0 29 my ($self, $bin, $mask, $length) = @_;
192 9     9   914 use bytes;
  9         13  
  9         26  
193              
194 21   66     45 $length //= usize($mask);
195 21 50       37 confess "Can`t get chunk of length $length" if $length > length $$bin;
196              
197 21         32 my $chunk = substr $$bin, 0 => $length, '';
198 21         90 return unpack $mask => $chunk;
199             }
200              
201             =head2 stream \$bin
202              
203             Parse incoming stream and creates packages from it.
204             If the data is not sufficient to create the package: returns the number
205             of data as many more as required.
206             The buffer is trimmed by the size of the created package.
207              
208             Return:
209              
210             =over
211              
212             =item undef, $need
213              
214             if decode in process and need more data
215              
216             =item object
217              
218             if the packet is fully decoded
219              
220             =item error code
221              
222             if there are any problems
223              
224             =cut
225              
226             sub stream {
227 9     9 1 13491 my ($class, $bin) = @_;
228 9     9   851 use bytes;
  9         20  
  9         27  
229              
230             # Need first 10 bytes
231 9         9 my $need = 10;
232 9 100       25 return (undef, $need) if $need > length $$bin;
233              
234             # Packet size
235 8         19 my $HL = unpack 'C' => substr $$bin, 3, usize('C');
236 8         16 my $FDL = unpack 'S' => substr $$bin, 5, usize('S');
237              
238             # Need full package size
239 8 50       17 $need = $HL + $FDL + ($FDL ? 2 : 0);
240 8 100       26 return (undef, $need) if $need > length $$bin;
241              
242 7         13 my $packet = substr $$bin, 0, $need, '';
243              
244             # Packet type
245 7         13 my $PT = unpack 'C' => substr $packet, 9, usize('C');
246              
247             # Create packet
248 7         14 my $subclass = $TYPES{ $PT };
249 7         23 load $subclass;
250 7         450 return $subclass->new->decode( \$packet );
251             }
252              
253             =head2 decode $bin
254              
255             Decode binary stream I<$bin> into packet object.
256             Return:
257              
258             =over
259              
260             =item undef, $need
261              
262             if decode in process and need more data
263              
264             =item object
265              
266             if the packet is fully decoded
267              
268             =item error code
269              
270             if there are any problems
271              
272             =back
273              
274             =cut
275              
276             sub decode {
277 12     12 1 103 my ($self, $bin) = @_;
278 9     9   1480 use bytes;
  9         15  
  9         30  
279              
280 12         60 $self->PRV( $self->take($bin => 'C') );
281 12         33 $self->SKID($self->take($bin => 'C') );
282              
283 12         26 my $flags = $self->take($bin => 'C');
284 12         52 $self->PRF( ($flags & 0b11000000) >> 6 );
285 12         42 $self->RTE( ($flags & 0b00100000) >> 5 );
286 12         37 $self->ENA( ($flags & 0b00011000) >> 3 );
287 12         34 $self->CMP( ($flags & 0b00000100) >> 2 );
288 12         41 $self->PRIORITY( ($flags & 0b00000011) );
289              
290 12         30 $self->HL( $self->take($bin => 'C') );
291 12         29 $self->HE( $self->take($bin => 'C') );
292 12         27 $self->FDL( $self->take($bin => 'S') );
293 12         28 $self->PID( $self->take($bin => 'S') );
294 12         27 $self->PT( $self->take($bin => 'C') );
295              
296 12 50       40 return EGTS_PC_UNS_PROTOCOL unless $self->PRV == 0x01;
297 12 50 33     52 return EGTS_PC_INC_HEADERFORM unless $self->HL == 11 || $self->HL == 16;
298 12 50       43 return EGTS_PC_UNS_PROTOCOL unless $self->PRF == 0x00;
299              
300 12 50       47 if( $self->RTE ) {
301 0         0 $self->PRA( $self->take($bin => 'S') );
302 0         0 $self->RCA( $self->take($bin => 'S') );
303 0         0 $self->TTL( $self->take($bin => 'C') );
304              
305 0         0 die 'RTE not supported';
306             }
307              
308             # Header CRC8
309 12         25 my $hsc = $self->take($bin => 'C');
310 12 50       60 return EGTS_PC_HEADERCRC_ERROR unless $self->HCS == $hsc;
311              
312             # Complete package. No data.
313 12 100       44 return $self unless $self->FDL;
314              
315 10         29 $self->SFRD( $self->take($bin => 'a*' => $self->FDL) );
316              
317 10         99 my $sfrcs = $self->take($bin => 'S');
318 10 50       38 return EGTS_PC_DATACRC_ERROR unless $self->SFRCS == $sfrcs;
319              
320 10 50       39 unless( $self->ENA == 0x00 ) {
321 0         0 warn 'Encryption not supported yet';
322 0         0 return EGTS_PC_DECRYPT_ERROR;
323             }
324              
325 10 50       33 unless( $self->CMP == 0x00 ) {
326 0         0 warn 'Compression not supported yet';
327 0         0 return EGTS_PC_INC_DATAFORM;
328             }
329              
330 10         82 return $self;
331             }
332              
333             =head2 encode
334              
335             Build packet as binary
336              
337             =cut
338              
339             sub encode {
340 11     11 1 549 my ($self) = @_;
341 9     9   2765 use bytes;
  9         10  
  9         28  
342              
343 11 50       50 croak 'Encryption not supported yet' if $self->ENA;
344 11 50       55 croak 'Compression not supported yet' if $self->CMP;
345 11 50       41 croak 'Packet Type required' unless defined $self->PT;
346              
347 11         23 my $mask = 'C C B8 C C S S C';
348              
349             # Optional fields
350 11         18 my @optional;
351 11 50 33     127 if( $self->PRA || $self->RCA || $self->TTL ) {
      33        
352 0         0 $mask .= ' S S C ';
353 0         0 push @optional, $self->PRA;
354 0         0 push @optional, $self->RCA;
355 0         0 push @optional, $self->TTL;
356              
357 0         0 $self->RTE( 0x1 );
358             }
359              
360             # Header Length
361 11 50       100 $self->HL( 10 + ($self->RTE ? 5 : 0) + 1 );
362              
363             # Build base header
364 11         251 my $bin = pack $mask =>
365             $self->PRV, $self->SKID,
366             sprintf(
367             '%02b%b%02b%b%02b',
368             $self->PRF, $self->RTE, $self->ENA, $self->CMP, $self->PRIORITY,
369             ),
370             $self->HL, $self->HE, $self->FDL, $self->PID, $self->PT,
371             @optional,
372             ;
373              
374             # Header Check Sum
375 11         37 $self->HCS( crc8 $bin );
376 11         47 $bin .= pack 'C' => $self->HCS;
377              
378             # Service Frame Data
379 11 100       46 $bin .= $self->SFRD if defined $self->SFRD;
380              
381             # Service Frame Data Check Sum
382 11 100 66     49 if( $self->SFRD && $self->FDL ) {
383 9         37 $bin .= pack 'S' => $self->SFRCS;
384             }
385              
386 11         39 $self->bin( $bin );
387 11         38 return $bin;
388             }
389              
390             =head2 as_debug
391              
392             Return human readable string
393              
394             =cut
395              
396             sub as_debug {
397 9     9 1 4236 my ($self) = @_;
398 9     9   2203 use bytes;
  9         17  
  9         25  
399              
400 9         161 my @bytes = ((unpack('B*', $self->bin)) =~ m{.{8}}g);
401              
402 9         18 my @str;
403 9         28 push @str => sprintf('PRV: %s', splice @bytes, 0 => usize('C'));
404 9         24 push @str => sprintf('SKID: %s', splice @bytes, 0 => usize('C'));
405 9         26 push @str => sprintf('FLAGS: %s', splice @bytes, 0 => usize('C'));
406 9         23 push @str => sprintf('HL: %s', splice @bytes, 0 => usize('C'));
407 9         20 push @str => sprintf('HE: %s', splice @bytes, 0 => usize('C'));
408 9         26 push @str => sprintf('FDL: %s %s', splice @bytes, 0 => usize('S'));
409 9         31 push @str => sprintf('PID: %s %s', splice @bytes, 0 => usize('S'));
410 9         53 push @str => sprintf('PT: %s', splice @bytes, 0 => usize('C'));
411              
412 9 50       61 push @str => sprintf('PRA: %s %s', splice @bytes, 0 => usize('S'))
413             if defined $self->PRA;
414 9 50       41 push @str => sprintf('RCA: %s %s', splice @bytes, 0 => usize('S'))
415             if defined $self->RCA;
416 9 50       58 push @str => sprintf('TTL: %s', splice @bytes, 0 => usize('C'))
417             if defined $self->TTL;
418              
419 9         27 push @str => sprintf('HCS: %s', splice @bytes, 0 => usize('C'));
420              
421 9 100       42 if( @bytes ) {
422              
423 6 50       29 if( my @qualify = inner() ) {
424 6         16 splice @bytes, 0 => -2;
425 6         15 push @str => sprintf('SFRD =>');
426 6         11 push @str, @qualify;
427 6         11 push @str => sprintf('<======');
428             } else {
429 0         0 my $it = natatime 4, splice @bytes, 0 => -2;
430 0         0 my @chunks;
431 0         0 while (my @vals = $it->()) {
432 0         0 push @chunks, join(' ', @vals);
433             }
434 0         0 push @str => sprintf('SFRD: %s', join("\n ", @chunks));
435             }
436              
437 6         21 push @str => sprintf('SFRCS: %s %s', splice @bytes, 0 => 2);
438             }
439              
440 9         54 push @str, sprintf '(Data %d bytes. Total %d bytes.)',
441             $self->FDL,
442             length $self->bin
443             ;
444              
445 9         76 return join "\n", @str;
446             }
447              
448             __PACKAGE__->meta->make_immutable();