File Coverage

blib/lib/BACnet/DataTypes/Utils.pm
Criterion Covered Total %
statement 230 283 81.2
branch 82 112 73.2
condition 33 44 75.0
subroutine 42 49 85.7
pod n/a
total 387 488 79.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package BACnet::DataTypes::Utils;
4              
5 27     27   168 use warnings;
  27         50  
  27         1487  
6 27     27   184 use strict;
  27         50  
  27         566  
7              
8 27     27   25503 use POSIX;
  27         207245  
  27         189  
9              
10             require BACnet::DataTypes::BitString;
11             require BACnet::DataTypes::Bool;
12             require BACnet::DataTypes::Date;
13             require BACnet::DataTypes::Double;
14             require BACnet::DataTypes::Enum;
15             require BACnet::DataTypes::Int;
16             require BACnet::DataTypes::Null;
17             require BACnet::DataTypes::ObjectIdentifier;
18             require BACnet::DataTypes::OctetString;
19             require BACnet::DataTypes::Real;
20             require BACnet::DataTypes::SequenceValue;
21             require BACnet::DataTypes::SequenceOfValues;
22             require BACnet::DataTypes::Time;
23             require BACnet::DataTypes::UnsignedInt;
24             require BACnet::DataTypes::CharString;
25             require BACnet::DataTypes::Choice;
26             require BACnet::DataTypes::DataType;
27              
28             #BACnet tags of dataTypes (p. 378 in doc)
29             use constant {
30 27         4826 NULL_TAG => 0x00,
31             BOOL_TAG => 0x01,
32             UNSIGNED_INT_TAG => 0x02,
33             SIGNED_INT_TAG => 0x03,
34             REAL_TAG => 0x04,
35             DOUBLE_TAG => 0x05,
36             OCTET_STRING_TAG => 0x06,
37             CHARACTER_STRING_TAG => 0x07,
38             BIT_STRING_TAG => 0x08,
39             ENUMERATED_TAG => 0x09,
40             DATE_TAG => 0x0A,
41             TIME_TAG => 0x0B,
42             OBJECT_ID_TAG => 0x0C,
43             EXTENDED_TAG => 0x0F
44 27     27   88955 };
  27         53  
45              
46             use constant {
47 27         1724 OPENING_LVT => 0x06,
48             CLOSING_LVT => 0x07,
49 27     27   164 };
  27         44  
50              
51             #generally useful constants
52 27     27   142 use constant { BYTE_MODULAR => ( 2**8 ) };
  27         124  
  27         1931  
53              
54             use constant {
55 27         2564 LVT_TRD_EXTENSION_MIN_SIZE => 65536,
56             LVT_SND_EXTENSION_MIN_SIZE => 254,
57             LVT_FST_EXTENSION_MIN_SIZE => 5,
58             LVT_TRD_EXTENDER => 255,
59             LVT_SND_EXTENDER => 254,
60             LVT_EXTENDER_TAG => 0x05,
61 27     27   328 };
  27         65  
62              
63 27     27   191 use constant { MAX_POSSIBLE_HEAD_SIZE => 7, };
  27         65  
  27         93934  
64              
65             our %tag_to_class = (
66             NULL_TAG() => 'BACnet::DataTypes::Null',
67             BOOL_TAG() => 'BACnet::DataTypes::Bool',
68             UNSIGNED_INT_TAG() => 'BACnet::DataTypes::UnsignedInt',
69             SIGNED_INT_TAG() => 'BACnet::DataTypes::Int',
70             REAL_TAG() => 'BACnet::DataTypes::Real',
71             DOUBLE_TAG() => 'BACnet::DataTypes::Double',
72             OCTET_STRING_TAG() => 'BACnet::DataTypes::OctetString',
73             CHARACTER_STRING_TAG() => 'BACnet::DataTypes::CharString',
74             BIT_STRING_TAG() => 'BACnet::DataTypes::BitString',
75             ENUMERATED_TAG() => 'BACnet::DataTypes::Enum',
76             DATE_TAG() => 'BACnet::DataTypes::Date',
77             TIME_TAG() => 'BACnet::DataTypes::Time',
78             OBJECT_ID_TAG() => 'BACnet::DataTypes::ObjectIdentifier',
79             );
80              
81             our @dt_types = ( NULL_TAG .. OBJECT_ID_TAG );
82              
83             sub _is_normalized_bool {
84 0     0   0 my ($bool_in) = @_;
85              
86 0 0 0     0 if ( $bool_in != 0 && $bool_in != 1 ) {
87 0         0 return 0;
88             }
89              
90 0         0 return 1;
91             }
92              
93             sub _normalize_bool {
94 7     7   19 my ($bool_in) = @_;
95              
96 7 100 100     56 if ( $bool_in != 0 && $bool_in != 1 ) {
97 2         6 $bool_in = 1; #0 = FALSE, everything else = TRUE
98             }
99              
100 7         18 return $bool_in;
101             }
102              
103             sub _encode_int {
104              
105 47     47   110 my ($input_int) = @_;
106 47         73 my ( $len_in_octets, $encoded_int );
107              
108 47 100       120 if ( $input_int < 0 ) {
109 10         23 ( $len_in_octets, $encoded_int ) = _encode_negative_int( $input_int, 1 );
110             }
111             else {
112 37         101 ( $len_in_octets, $encoded_int ) =
113             _encode_nonnegative_int( $input_int, 1 );
114             }
115              
116 47         177 return ( $len_in_octets, $encoded_int );
117             }
118              
119             sub _encode_int_octet_undef {
120 72     72   126 my ( $input_int, $undef_identifier ) = @_;
121              
122 72 100       136 if ( !defined $input_int ) {
123 8         27 return pack( 'C', $undef_identifier );
124             }
125              
126 64         188 return pack( 'C', $input_int );
127             }
128              
129             sub _encode_int_octet {
130 0     0   0 my ($input_int) = @_;
131 0         0 return pack( 'C', _encode_int($input_int) & 0xFF );
132             }
133              
134             sub _encode_nonnegative_int {
135              
136 140     140   332 my ( $input_int, $sign_wrap ) = @_;
137 140         254 my @encoded = ();
138 140         268 my $len_in_octets = 0;
139              
140 140         220 while (1) {
141 180         788 push( @encoded, pack( 'C', $input_int % BYTE_MODULAR ) );
142              
143 180         681 $input_int = floor( $input_int / BYTE_MODULAR );
144              
145 180         297 $len_in_octets++;
146              
147 180 100       528 if ( $input_int == 0 ) {
148 140         347 last;
149             }
150             }
151              
152 140 100 100     559 if ( defined($sign_wrap)
153             && ( unpack( 'C', $encoded[-1] ) & 0x80 ) == 0x80 )
154             {
155 3         8 push( @encoded, pack( 'C', 0 ) );
156 3         5 $len_in_octets++;
157             }
158              
159 140         767 return ( $len_in_octets, join( '', ( reverse @encoded ) ) );
160             }
161              
162             sub _encode_negative_int {
163              
164 10     10   17 my ( $input_int, $sign_wrap ) = @_;
165              
166 10         15 my $to_encode = -$input_int - 1;
167 10         14 my @encoded = ();
168 10         11 my $len_in_octets = 0;
169              
170 10         10 while (1) {
171 16         55 push( @encoded,
172             pack( 'C', ( ~int( $to_encode % BYTE_MODULAR ) ) & 0xFF ) )
173             ; #0xFF is here in case of overflow during conversion cause by inverting whole int not just 32 bytes
174              
175 16         44 $to_encode = floor( $to_encode / BYTE_MODULAR );
176              
177 16         18 $len_in_octets++;
178              
179 16 100       35 if ( $to_encode == 0 ) {
180 10         16 last;
181             }
182             }
183              
184 10 100 66     53 if (
185             ( defined($sign_wrap) && ( unpack( 'C', $encoded[-1] ) & 0x80 ) == 0 ) )
186             {
187 4         8 push @encoded, pack( 'C', 0xFF );
188 4         5 $len_in_octets++;
189             }
190              
191 10         46 return ( $len_in_octets, join( '', ( reverse @encoded ) ) );
192             }
193              
194             sub _decode_int {
195 56     56   158 my ($coded_int) = @_;
196              
197 56         129 my $first_octet = unpack( 'C', substr( $coded_int, 0, 1 ) );
198              
199 56         82 my $result = 0;
200 56 100       133 if ( ( $first_octet & 0x80 ) == 0x80 ) {
201 10         25 $result = _decode_negative_int($coded_int);
202             }
203             else {
204 46         95 $result = _decode_nonnegative_int($coded_int);
205             }
206 56         153 return $result;
207             }
208              
209             sub _decode_int_octet_undef {
210 81     81   224 my ( $coded_int, $undef_identifier ) = @_;
211              
212 81 100       193 if ( $coded_int eq pack( 'C', $undef_identifier ) ) {
213 8         23 return undef;
214             }
215              
216 73         201 return unpack( 'C', $coded_int );
217             }
218              
219             sub _decode_nonnegative_int {
220 152     152   431 my ($coded_int) = @_;
221 152         427 my @data = unpack( 'C*', $coded_int );
222              
223 152         391 return _decode_nonnegative_int_b(@data);
224             }
225              
226             sub _decode_nonnegative_int_b {
227              
228 897     897   1885 my @data = @_;
229 897         1422 my $result = 0;
230              
231 897         2542 for ( my $i = scalar(@data) - 1 ; $i >= 0 ; $i-- ) {
232 1020         3447 $result += $data[$i] * ( 256**( scalar(@data) - $i - 1 ) );
233             }
234              
235 897         2000 return $result;
236             }
237              
238             sub _decode_negative_int {
239 10     10   22 my ($coded_int) = @_;
240              
241 10         21 my @data = unpack( 'C*', $coded_int );
242 10         12 my $result = 0;
243              
244 10         31 for ( my $i = scalar(@data) - 1 ; $i >= 0 ; $i-- ) {
245 20         32 my $inverted = ( ~$data[$i] ) & 0xFF;
246 20         54 $result += $inverted * ( 256**( scalar(@data) - $i - 1 ) );
247             }
248              
249 10         19 return -( $result + 1 );
250             }
251              
252             sub _upper_bound_division {
253 122     122   365 my ( $nominator, $denominator ) = @_;
254              
255 122         651 my $result = POSIX::ceil( $nominator / $denominator );
256              
257 122         591 return $result;
258             }
259              
260             sub _add_coma {
261 0     0   0 my ($string) = @_;
262              
263 0 0       0 if ( $string ne "" ) {
264 0         0 return "$string, ";
265             }
266              
267 0         0 return $string;
268             }
269              
270             sub _extend_headache {
271 0     0   0 my ( $headache, $error ) = @_;
272              
273 0         0 $headache = _add_coma($headache);
274 0         0 $headache .= $error;
275              
276 0         0 return $headache;
277              
278             }
279              
280             sub _get_head_length {
281              
282 1466     1466   2623 my ($data_in) = @_;
283              
284 1466         2738 my $potential_head = _get_potential_head($data_in);
285              
286 1466         3476 my @bytes = unpack( "C*", $potential_head );
287              
288 1466         3074 return _get_head_length_b(@bytes);
289             }
290              
291             sub _get_head_length_b {
292              
293 1466     1466   2921 my (@bytes) = @_;
294 1466         2182 my $len = 1;
295              
296 1466 50       3551 if ( !defined $bytes[ $len - 1 ] ) {
297 0         0 return -1;
298             }
299              
300 1466 100       3552 if ( _is_tag_extended( $bytes[ $len - 1 ] ) ) {
301 34         67 $len++;
302             }
303              
304 1466 100       3451 if ( ( $bytes[0] & 0x07 ) == LVT_EXTENDER_TAG ) {
305 589         927 $len++;
306              
307 589 50       1365 if ( !defined $bytes[ $len - 1 ] ) {
308 0         0 return -1;
309             }
310              
311 589 100       4830 if ( $bytes[ $len - 1 ] == LVT_TRD_EXTENDER ) {
    100          
312 2         4 $len += 4;
313             }
314             elsif ( $bytes[ $len - 1 ] == LVT_SND_EXTENDER ) {
315 79         137 $len += 2;
316             }
317             }
318              
319 1466         13826 return $len;
320             }
321              
322             sub _get_head_tag {
323              
324 1468     1468   3146 my ($data_in) = @_;
325              
326 1468         3077 my $potential_head = _get_potential_head($data_in);
327              
328 1468         4034 my @bytes = unpack( "C*", $potential_head );
329              
330 1468         3457 return _get_head_tag_b(@bytes);
331             }
332              
333             sub _get_head_tag_b {
334              
335 1468     1468   3211 my (@bytes) = @_;
336              
337 1468 50       3486 if ( !defined $bytes[0] ) {
338 0         0 return -1;
339             }
340              
341 1468         2723 my $tag = ( $bytes[0] & 0xF0 ) >> 4;
342              
343 1468 100       3228 if ( $tag == EXTENDED_TAG ) {
344 39 50       109 if ( !defined $bytes[1] ) {
345 0         0 return -1;
346             }
347              
348 39         121 $tag = $bytes[1];
349             }
350              
351 1468         6176 return $tag;
352             }
353              
354             sub _get_head_ac_class {
355              
356 1612     1612   3392 my ($data_in) = @_;
357              
358 1612         3232 my $potential_head = _get_potential_head($data_in);
359              
360 1612         4855 my @bytes = unpack( "C*", $potential_head );
361              
362 1612         3810 return _get_head_ac_class_b(@bytes);
363              
364             }
365              
366             sub _get_head_ac_class_b {
367              
368 1612     1612   3372 my (@bytes) = @_;
369              
370 1612 50       4070 if ( !defined $bytes[0] ) {
371 0         0 return -1;
372             }
373              
374 1612         7382 return ( ( $bytes[0] & 0x08 ) >> 3 );
375             }
376              
377             sub _get_head_lvt {
378              
379 2075     2075   3711 my ($data_in) = @_;
380              
381 2075         4162 my $potential_head = _get_potential_head($data_in);
382              
383 2075         5385 my @bytes = unpack( "C*", $potential_head );
384              
385 2075         4659 return _get_head_lvt_b(@bytes);
386             }
387              
388             sub _get_head_lvt_b {
389              
390 2075     2075   4212 my (@bytes) = @_;
391              
392 2075 50       4562 if ( !defined $bytes[0] ) {
393 0         0 return -1;
394             }
395              
396 2075         3712 my $lvt = $bytes[0] & 0x07;
397              
398 2075 100       4549 if ( $lvt == LVT_EXTENDER_TAG ) {
399              
400 745         1125 my $lvt_ext_position = 1;
401              
402 745 100       1680 if ( _is_tag_extended( $bytes[0] ) ) {
403 8         15 $lvt_ext_position++;
404             }
405              
406 745 50       1767 if ( !defined $bytes[$lvt_ext_position] ) {
407 0         0 return -1;
408             }
409              
410 745 100       2106 if ( $bytes[$lvt_ext_position] == LVT_TRD_EXTENDER ) {
    100          
411              
412 4 50       15 if ( !defined $bytes[ $lvt_ext_position + 1 + 3 ] ) {
413 0         0 return -1;
414             }
415              
416 4         20 $lvt = _decode_nonnegative_int_b(
417             @bytes[
418             ( $lvt_ext_position + 1 ) ... ( $lvt_ext_position + 1 + 3 )
419             ]
420             );
421             }
422             elsif ( $bytes[$lvt_ext_position] == LVT_SND_EXTENDER ) {
423              
424 68 50       199 if ( !defined $bytes[ $lvt_ext_position + 1 + 1 ] ) {
425 0         0 return -1;
426             }
427              
428 68         220 $lvt = _decode_nonnegative_int_b(
429             @bytes[
430             ( $lvt_ext_position + 1 ) ... ( $lvt_ext_position + 1 + 1 )
431             ]
432             );
433              
434             }
435             else {
436 673         2046 $lvt = _decode_nonnegative_int_b(
437             @bytes[ ($lvt_ext_position) ... ($lvt_ext_position) ] );
438              
439             }
440             }
441              
442 2075         8002 return $lvt;
443             }
444              
445             sub _is_lvt_extended {
446              
447 54     54   108 my ($data_in) = @_;
448              
449 54         117 my $potential_head = _get_potential_head($data_in);
450 54         132 my @bytes = unpack( "C*", $potential_head );
451              
452 54 50       121 if ( !defined $bytes[0] ) {
453 0         0 return -1;
454             }
455              
456 54         85 my $lvt = $bytes[0] & 0x07;
457              
458 54 50       105 if ( $lvt == LVT_EXTENDER_TAG ) {
459 0         0 return 1;
460             }
461              
462 54         167 return 0;
463             }
464              
465             sub _is_context_sequence {
466              
467 55     55   108 my ($data_in) = @_;
468              
469 55 100 100     140 if ( _get_head_ac_class($data_in) == 1
      66        
470             && _get_head_lvt($data_in) == OPENING_LVT
471             && _is_lvt_extended($data_in) == 0 )
472             {
473 25         78 return 1;
474             }
475              
476 30         99 return 0;
477             }
478              
479             sub _is_end_of_context_sequence {
480              
481 161     161   349 my ($data_in) = @_;
482              
483 161 100 100     294 if ( _get_head_ac_class($data_in) == 1
      66        
484             && _get_head_lvt($data_in) == CLOSING_LVT
485             && _is_lvt_extended($data_in) == 0 )
486             {
487 29         156 return 1;
488             }
489              
490 132         524 return 0;
491             }
492              
493             sub _parse_any_dt
494             { #actually do not work on ever single dt, just on primitive ones
495              
496 0     0   0 my ($data_in) = @_;
497              
498 0         0 my $potential_head = _get_potential_head($data_in);
499              
500 0         0 my $tag = _get_head_tag($potential_head);
501 0         0 my $dt_class = $tag_to_class{$tag};
502              
503 0 0       0 if ( !defined $dt_class ) {
504 0         0 return undef;
505             }
506              
507 0         0 my $len = _get_head_length($potential_head);
508              
509 0 0       0 if ( $tag != BOOL_TAG ) {
510 0         0 $len += _get_head_lvt($potential_head);
511             }
512              
513 0         0 return $dt_class->parse( substr( $data_in, 0, $len ) );
514             }
515              
516             sub _parse_context_dt {
517 123     123   317 my ( $data_in, $bone ) = @_;
518              
519              
520 123 100 100     715 if ( $bone->{dt} eq 'BACnet::DataTypes::SequenceOfValues'
      100        
521             || $bone->{dt} eq 'BACnet::DataTypes::SequenceValue'
522             || $bone->{dt} eq 'BACnet::DataTypes::Choice' )
523             {
524             return $bone->{dt}
525 27         151 ->parse( $data_in, $bone->{skeleton}, $bone->{wrapped} );
526             }
527              
528             return $bone->{dt}->parse(
529 96         212 substr(
530             $data_in, 0, _get_head_lvt($data_in) + _get_head_length($data_in)
531             ),
532             );
533              
534             }
535              
536             sub _get_head_metadata {
537 0     0   0 my ($data_in) = @_;
538              
539 0         0 my $potential_head = _get_potential_head($data_in);
540              
541 0         0 my @bytes = unpack( "C*", $potential_head );
542              
543 0         0 my %head_metadata = (
544             length => _get_head_length_b(@bytes),
545             tag => _get_head_tag_b(@bytes),
546             ac_class => _get_head_ac_class_b(@bytes),
547             lvt => _get_head_lvt_b(@bytes),
548             );
549              
550 0         0 return %head_metadata;
551              
552             }
553              
554             sub _is_tag_extended {
555 2211     2211   3900 my ($base_head) = @_;
556 2211         6135 return ( ( ( $base_head & 0xF0 ) >> 4 ) == EXTENDED_TAG );
557             }
558              
559             sub _correct_head {
560 498     498   4028 my %args = (
561             data_in => undef,
562             expected_tag => undef,
563             expected_length => undef,
564             lvt_expected_value => undef,
565             lvt_is_length => 1,
566             @_,
567             );
568 498         1140 my $data_in = $args{data_in};
569 498         998 my $expected_tag = $args{expected_tag};
570 498         918 my $expected_length = $args{expected_length};
571 498         864 my $lvt_expected_value = $args{lvt_expected_value};
572              
573 498         907 my $headache = "";
574              
575 498 50       1442 if ( !_correct_tag( $data_in, $expected_tag ) ) {
576 0         0 $headache = _extend_headache( $headache, "invalid tag" );
577             }
578              
579 498 50       1450 if ( !_correct_lvt( $data_in, $lvt_expected_value ) ) {
580 0         0 $headache = _extend_headache( $headache, "invalid lvt" );
581             }
582              
583 498 50 66     2088 if ( $args{lvt_is_length} == 1
584             && !_current_length( $data_in, _get_head_lvt( $args{data_in} ) ) )
585             {
586 0         0 $headache = _extend_headache( $headache, "invalid lvt length" );
587             }
588              
589 498 50 66     1536 if ( defined($expected_length)
590             && !_current_length( $data_in, $expected_length ) )
591             {
592 0         0 $headache = _extend_headache("invalid static length");
593             }
594              
595 498         2314 return $headache;
596             }
597              
598             sub _correct_tag {
599 498     498   1222 my ( $data_in, $tag ) = @_;
600              
601 498 100 66     1341 if ( ( _get_head_ac_class($data_in) == 0 )
602             && ( _get_head_tag($data_in) == $tag ) )
603             {
604 412         1487 return 1;
605             }
606              
607 86 50 33     192 if ( ( _get_head_ac_class($data_in) == 1 )
608             && ( _get_head_tag($data_in) != -1 ) )
609             {
610 86         259 return 1;
611             }
612              
613 0         0 return 0;
614             }
615              
616             sub _correct_lvt {
617 498     498   1119 my ( $data_in, $lvt_expected_value ) = @_;
618              
619 498 50       1282 if ( !defined $lvt_expected_value ) {
620 498         1270 return _get_head_lvt($data_in) != -1;
621             }
622              
623 0 0       0 if ( _get_head_lvt($data_in) != $lvt_expected_value ) {
624 0         0 return 0;
625             }
626              
627 0         0 return 1;
628             }
629              
630             sub _current_length {
631 625     625   1261 my ( $data_in, $length_expected ) = @_;
632              
633 625 50       1588 if ( length($data_in) == $length_expected + _get_head_length($data_in) ) {
634 625         2291 return 1;
635             }
636              
637 0         0 return 0;
638             }
639              
640             sub _make_head {
641 524     524   1370 my ( $tag, $ac_class, $lvt, $short_lvt ) = @_;
642 524   100     2597 $short_lvt //= 0;
643              
644 524         936 my $head_tag = $tag;
645 524         884 my $head_lvt = $lvt;
646              
647 524 100       1418 if ( $tag >= EXTENDED_TAG ) {
648 13         31 $head_tag = EXTENDED_TAG;
649             }
650 524 100       1296 if ( $short_lvt == 0 ) {
651 482 100       1227 if ( $lvt >= LVT_FST_EXTENSION_MIN_SIZE ) {
652 184         425 $head_lvt = LVT_EXTENDER_TAG;
653             }
654             }
655              
656 524         2449 my $head =
657             pack( 'C', ( $head_tag << 4 ) | ( $ac_class << 3 ) | ($head_lvt) );
658              
659 524 100       1379 if ( $tag >= EXTENDED_TAG ) {
660 13         45 $head .= pack( 'C', $tag );
661             }
662              
663 524 100       1330 if ( $short_lvt == 1 ) {
664 42         111 return $head;
665             }
666              
667 482 100       2060 if ( $lvt >= LVT_TRD_EXTENSION_MIN_SIZE ) {
    100          
    100          
668 1         4 $head .= pack( 'C', LVT_TRD_EXTENDER );
669 1         7 $head .= pack( 'N', $lvt );
670             }
671             elsif ( $lvt >= LVT_SND_EXTENSION_MIN_SIZE ) {
672 17         41 $head .= pack( 'C', LVT_SND_EXTENDER );
673 17         59 $head .= pack( 'n', $lvt );
674              
675             }
676             elsif ( $lvt >= LVT_FST_EXTENSION_MIN_SIZE ) {
677 166         511 $head .= pack( 'C', $lvt );
678             }
679              
680 482         2236 return $head;
681             }
682              
683             sub _get_potential_head {
684 6675     6675   11068 my ($data_in) = @_;
685              
686 6675         15372 return substr( $data_in, 0, 7 );
687             }
688              
689             sub _get_tag_and_ac_class {
690              
691 475     475   1145 my ( $tag_in, $modified_tag ) = @_;
692              
693 475         872 my $ac_class = 0;
694 475         915 my $tag = $tag_in;
695              
696 475 100       1628 if ( defined $modified_tag ) {
697 79         126 $ac_class = 1;
698 79         129 $tag = $modified_tag;
699             }
700              
701 475         1582 return ( $tag, $ac_class );
702             }
703              
704             sub _construct_head {
705              
706 475     475   1373 my ( $tag_in, $modified_tag, $lvt, ) = @_;
707              
708 475         1487 my ( $tag, $ac_class ) =
709             BACnet::DataTypes::Utils::_get_tag_and_ac_class( $tag_in, $modified_tag );
710              
711 475         1635 return _make_head( $tag, $ac_class, $lvt );
712              
713             }
714              
715             sub _get_char_string_coding_type {
716 62     62   151 my ($data_in) = @_;
717              
718 62         181 return unpack( 'C', substr( $data_in, _get_head_length($data_in), 1 ) );
719             }
720              
721             sub _property_identifier_value_wrapper {
722 9     9   18 my ($bone) = @_;
723 9 100       31 if ( !defined $bone->{skeleton} ) {
724 7         31 $bone->set_name('value');
725 7         39 return BACnet::DataTypes::Bone->construct(
726             dt => 'BACnet::DataTypes::SequenceValue',
727             skeleton => [$bone],
728             );
729             }
730              
731 2         8 return $bone;
732             }
733              
734             sub _normalize_substitution {
735 0     0     my ( $substitution, $default_substitution ) = @_;
736              
737 0 0         if ( ref($substitution) eq 'HASH' ) {
738 0           return $substitution;
739             }
740 0           return $default_substitution;
741             }
742              
743             1;