File Coverage

blib/lib/DOCSIS/ConfigFile/Encode.pm
Criterion Covered Total %
statement 150 168 89.2
branch 37 70 52.8
condition 5 14 35.7
subroutine 25 26 96.1
pod 17 17 100.0
total 234 295 79.3


line stmt bran cond sub pod time code
1             package DOCSIS::ConfigFile::Encode;
2 17     17   136 use strict;
  17         44  
  17         541  
3 17     17   96 use warnings;
  17         48  
  17         476  
4 17     17   91 use bytes;
  17         171  
  17         195  
5 17     17   486 use Carp qw(confess);
  17         188  
  17         1040  
6 17     17   127 use Math::BigInt;
  17         39  
  17         119  
7 17     17   5709 use Socket;
  17         40  
  17         50096  
8              
9             our %SNMP_TYPE = (
10             INTEGER => [0x02, \&int],
11             STRING => [0x04, \&string],
12             NULLOBJ => [0x05, sub { }],
13             OBJECTID => [0x06, \&objectid],
14             IPADDRESS => [0x40, \&ip],
15             COUNTER => [0x41, \&uint],
16             UNSIGNED => [0x42, \&uint],
17             TIMETICKS => [0x43, \&uint],
18             OPAQUE => [0x44, \&uint],
19             COUNTER64 => [0x46, \&bigint],
20             );
21              
22             sub bigint {
23 1     1 1 959 my $value = _test_value(bigint => $_[0]);
24 1         9 my $int64 = Math::BigInt->new($value);
25              
26 1 50       160 $int64->is_nan and confess "$value is not a number";
27              
28 1         13 my $negative = $int64 < 0;
29 1 50       356 my @bytes = $negative ? (0x80) : ();
30              
31 1         5 while ($int64) {
32 8         204 my $value = $int64 & 0xff;
33 8         2063 $int64 >>= 8;
34 8 50       1846 $value ^= 0xff if ($negative);
35 8         28 unshift @bytes, $value;
36             }
37              
38 1 50       32 return @bytes ? @bytes : (0); # 0 is also a number ;-)
39             }
40              
41             sub ether {
42 7     7 1 1166 my $string = _test_value(ether => $_[0]);
43              
44 7 50       104 if ($string =~ qr{^\+?[0-4294967295]$}) { # numeric
    50          
45 0         0 return uint({value => $string});
46             }
47             elsif ($string =~ /^(?:0x)?([0-9a-f]+)$/i) { # hex
48 7         55 return hexstr({value => $1});
49             }
50              
51 0         0 confess "ether({ value => $string }) is invalid";
52             }
53              
54             sub hexstr {
55 23     23 1 886 my $string = _test_value(hexstr => $_[0], qr{(?:0x)?([a-f0-9]+)}i);
56 23         64 my @bytes;
57              
58 23         76 $string =~ s/^(?:0x)//;
59 23         678 unshift @bytes, hex $1 while $string =~ s/(\w{1,2})$//;
60 23 50       86 confess "hexstr({ value => ... }) is left with ($string) after decoding" if $string;
61 23         130 return @bytes;
62             }
63              
64 6     6 1 618 sub ip { split /\./, _test_value(ip => $_[0], qr{^(?:\d{1,3}\.){3}\d{1,3}$}) }
65              
66             sub int {
67 7     7 1 2221 my $obj = $_[0];
68 7         32 my $int = _test_value(int => $obj, qr{^[+-]?\d{1,10}$});
69 7         25 my $negative = $int < 0;
70 7         21 my @bytes;
71              
72             # make sure we're working on 32bit
73 7         20 $int &= 0xffffffff;
74              
75 7         20 while ($int) {
76 16         25 my $value = $int & 0xff;
77 16         28 $int >>= 8;
78 16 100       42 $value ^= 0xff if ($negative);
79 16         40 unshift @bytes, $value;
80             }
81              
82 7 100       23 if (!$obj->{snmp}) {
83 2 100       7 $bytes[0] |= 0x80 if ($negative);
84 2         8 unshift @bytes, 0 for (1 .. 4 - @bytes);
85             }
86 7 50       20 if (@bytes == 0) {
87 0         0 @bytes = (0);
88             }
89 7 100       22 if ($obj->{snmp}) {
90 5 50 33     29 unshift @bytes, 0 if (!$negative and $bytes[0] > 0x79);
91             }
92              
93 7         30 return @bytes;
94             }
95              
96       9 1   sub mic { }
97       1 1   sub no_value { }
98              
99             sub objectid {
100 0     0 1 0 my $oid = _test_value(objectid => $_[0], qr{^\.?\d+(\.\d+)+$});
101 0         0 $oid =~ s/^\.//;
102 0         0 return _snmp_oid($oid);
103             }
104              
105             sub snmp_object {
106 11     11 1 147 my $obj = _test_value(snmp_object => $_[0]);
107 11 50 0     81 my $type = $SNMP_TYPE{uc($obj->{type})} or confess "Unknown SNMP type: @{[$obj->{type}||'']}";
  0         0  
108 11         55 my @value = $type->[1]->({value => $obj->{value}, snmp => 1});
109 11         46 my @oid = _snmp_oid($obj->{oid});
110              
111 11 50       34 unless (@value) {
112 0         0 confess 'Failed to decode SNMP value: ' . $obj->{value};
113             }
114              
115 11         36 my @oid_length = _snmp_length(0 + @oid);
116 11         33 my @value_length = _snmp_length(0 + @value);
117 11         57 my @total_length = _snmp_length(3 + @value + @oid + @value_length);
118              
119             return (
120             #-type--------length----------value-----type---
121 11         97 0x30, @total_length, # object
122             0x06, @oid_length, @oid, # oid
123             $type->[0], @value_length, @value, # value
124             );
125             }
126              
127             sub string {
128 21     21 1 1234 my $string = _test_value(string => $_[0]);
129 21 100       97 return hexstr(@_) if $string =~ /^0x[a-f0-9]+$/i;
130 18         48 $string =~ s/%(\w\w)/{ chr hex $1 }/ge;
  2         14  
  2         18  
131 18         112 return map { ord $_ } split //, $string;
  235         389  
132             }
133              
134             sub stringz {
135 2     2 1 8 my @bytes = string(@_);
136 2 50 33     34 push @bytes, 0 if (@bytes == 0 or $bytes[-1] ne "\0");
137 2         18 return @bytes;
138             }
139              
140 59     59 1 782 sub uchar { _test_value(uchar => $_[0], qr/\+?\d{1,3}$/) }
141              
142             sub uint {
143 52     52 1 646 my $obj = $_[0];
144 52         187 my $uint = _test_value(uint => $obj, qr{^\+?\d{1,10}$});
145 52         125 my @bytes;
146              
147 52         110 while ($uint) {
148 146         228 my $value = $uint & 0xff;
149 146         232 $uint >>= 8;
150 146         305 unshift @bytes, $value;
151             }
152              
153 52 50       122 if (!$obj->{snmp}) {
154 52         163 unshift @bytes, 0 for (1 .. 4 - @bytes);
155             }
156 52 50       125 if (@bytes == 0) {
157 0         0 @bytes = (0);
158             }
159 52 50       111 if ($obj->{snmp}) {
160 0 0       0 unshift @bytes, 0 if ($bytes[0] > 0x79);
161             }
162              
163 52         241 return @bytes;
164             }
165              
166             sub ushort {
167 56     56 1 642 my $obj = $_[0];
168 56         209 my $ushort = _test_value(ushort => $obj, qr{^\+?\d{1,5}$});
169 56         125 my @bytes;
170              
171 56 50 33     148 unshift @bytes, 0 if $obj->{snmp} and $ushort > 0x79;
172              
173 56         118 while ($ushort) {
174 49         84 my $value = $ushort & 0xff;
175 49         115 $ushort >>= 8;
176 49         123 unshift @bytes, $value;
177             }
178              
179 56 50       184 map { unshift @bytes, 0 } 1 .. 2 - @bytes unless $obj->{snmp};
  63         154  
180 56 50       380 return @bytes ? @bytes : (0);
181             }
182              
183             sub ushort_list {
184 1 50   1 1 2 map { ushort({value => $_}) } @{$_[0]->{value} || []};
  10         26  
  1         15  
185             }
186              
187             sub vendor {
188 3     3 1 7 my $options = $_[0]->{value}{options};
189 3         16 my @vendor = ether({value => $_[0]->{value}{id}});
190 3         15 my @bytes = (8, CORE::int(@vendor), @vendor);
191              
192 3         15 for (my $i = 0; $i < @$options; $i += 2) {
193 7         25 my @value = hexstr({value => $options->[$i + 1]});
194 7         30 push @bytes, uchar({value => $options->[$i]});
195 7         20 push @bytes, CORE::int(@value);
196 7         22 push @bytes, @value;
197             }
198              
199 3         23 return @bytes;
200             }
201              
202             sub vendorspec {
203 1     1 1 564 my $obj = $_[0];
204 1         2 my (@vendor, @bytes);
205              
206 1 50       7 confess "vendor({ nested => ... }) is not an array ref" unless ref $obj->{nested} eq 'ARRAY';
207              
208 1         6 @vendor = ether($obj); # will extract value=>$hexstr. might confess
209 1         6 @bytes = (8, CORE::int(@vendor), @vendor);
210              
211 1         2 for my $tlv (@{$obj->{nested}}) {
  1         4  
212 1         4 my @value = hexstr($tlv); # will extract value=>$hexstr. might confess
213 1         5 push @bytes, uchar({value => $tlv->{type}});
214 1         4 push @bytes, CORE::int(@value);
215 1         3 push @bytes, @value;
216             }
217              
218 1         6 return @bytes;
219             }
220              
221             sub _snmp_length {
222 33     33   60 my $length = $_[0];
223 33         65 my @bytes;
224              
225 33 50       92 return $length if $length < 0x80;
226 0 0       0 return 0x81, $length if $length < 0xff;
227 0 0       0 confess "Too long snmp length: ($length)" unless $length < 0xffff;
228              
229 0         0 while ($length) {
230 0         0 unshift @bytes, $length & 0xff;
231 0         0 $length >>= 8;
232             }
233              
234 0         0 return 0x82, @bytes;
235             }
236              
237             sub _snmp_oid {
238 11     11   29 my $oid = $_[0];
239 11         18 my (@encoded_oid, @input_oid);
240 11         20 my $subid = 0;
241              
242 11 50       41 if ($_[0] =~ /[A-Za-z]/) {
243 0         0 die "[DOCSIS] Need to install SNMP.pm http://www.net-snmp.org/ to encode non-numberic OID $oid"
244             unless DOCSIS::ConfigFile::CAN_TRANSLATE_OID;
245 0 0       0 $oid = SNMP::translateObj($oid) or confess "Could not translate OID '$_[0]'";
246             }
247              
248 11         66 @input_oid = split /\./, $oid;
249 11 50       34 shift @input_oid unless length $input_oid[0];
250              
251             # the first two sub-id are in the first id
252             {
253 11         19 my $first = shift @input_oid;
  11         23  
254 11         18 my $second = shift @input_oid;
255 11         42 push @encoded_oid, $first * 40 + $second;
256             }
257              
258             SUB_OID:
259 11         27 for my $id (@input_oid) {
260 113 100       212 if ($id <= 0x7f) {
261 109         203 push @encoded_oid, $id;
262             }
263             else {
264 4         11 my @suboid;
265              
266 4         14 while ($id) {
267 8         19 unshift @suboid, 0x80 | ($id & 0x7f);
268 8         17 $id >>= 7;
269             }
270              
271 4         9 $suboid[-1] &= 0x7f;
272 4         8 push @encoded_oid, @suboid;
273             }
274             }
275              
276 11         66 return @encoded_oid;
277             }
278              
279             sub _test_value {
280 243     243   526 my ($name, $obj, $test) = @_;
281              
282 243 50       533 confess "$name({ value => ... }) received undefined value" unless defined $obj->{value};
283             confess "$name({ value => " . $obj->{value} . " }) does not match $test"
284 243 50 66     1480 if $test and not $obj->{value} =~ $test;
285 243         823 $obj->{value};
286             }
287              
288             1;
289              
290             =encoding utf8
291              
292             =head1 NAME
293              
294             DOCSIS::ConfigFile::Encode - Encode functions for a DOCSIS config-file.
295              
296             =head1 DESCRIPTION
297              
298             L has functions which is used to encode "human"
299             data into list of unsigned characters (0-255) (refered to as "bytes") later in
300             the pod. This list can then be encoded into binary data using:
301              
302             $bytestr = pack 'C*', @uchar;
303              
304             =head1 FUNCTIONS
305              
306             =head2 bigint
307              
308             Returns a list of bytes representing the C<$bigint>. This can be any
309             number (negative or positive) which can be representing using 64 bits.
310              
311             =head2 ether
312              
313             This function use either L or L to encode the
314             input value. It will figure out the function to use by checking
315             the input for either integer value or a string looking like
316             a hex-string.
317              
318             =head2 hexstr
319              
320             Will encode any hex encoded string into a list of bytes. The string
321             can have an optional leading "0x".
322              
323             =head2 int
324              
325             Returns a list of bytes representing the C<$int>. This can be any
326             number (negative or positive) which can be representing using 32 bits.
327              
328             =head2 ip
329              
330             Returns a list of four bytes representing the C<$ip>. The C<$ip> must
331             be in in the format "1.2.3.4".
332              
333             =head2 objectid
334              
335             Encodes MIB number as value of C
336             can be in format: 1.2.3.4, .1.2.3.4
337              
338             =head2 mic
339              
340             Cannot encode CM/CMTS mic without complete information about
341             the config file, so this function returns an empty list.
342              
343             =head2 no_value
344              
345             This method will return an empty list. It is used by DOCSIS types, which
346             has zero length.
347              
348             =head2 snmp_object
349              
350             This function encodes a human-readable SNMP oid into a list of bytes:
351              
352             @bytes = (
353             #-type---length---------value-----type---
354             0x30, $total_length, # object
355             0x06, int(@oid), @oid, # oid
356             $type, int(@value), @value, # value
357             );
358              
359             =head2 string
360              
361             Returns a list of bytes representing the C<$str>. Will use
362             L to decode it if it looks like a hex string (a
363             string starting with leading "0x"). In other cases, it will
364             decode it itself. The input string might also be encoded
365             with a simple uri-encode format: "%20" will be translated
366             to a space, and "%25" will be translated into "%", before
367             encoded using C.
368              
369             =head2 stringz
370              
371             Returns a list of bytes representing the C<$str> with a zero
372             terminator at the end. The "\0" byte will be added unless
373             seen as the last element in the list.
374              
375             Only ServiceClassName needs this, see C<$DOCSIS::ConfigFile::TREE> for more
376             details.
377              
378             =head2 uchar
379              
380             Returns a list with one byte representing the C<$uchar>. This can be any
381             positive number which can be representing using 8 bits.
382              
383             =head2 uint
384              
385             Returns a list of bytes representing the C<$uint>. This can be any
386             positive number which can be representing using 32 bits.
387              
388             =head2 ushort
389              
390             Returns a list of bytes representing the C<$ushort>. This can be any
391             positive number which can be representing using 16 bits.
392              
393             =head2 ushort_list
394              
395             Returns a list of bytes representing the C<$ushort>. This can be any
396             positive number which can be representing using 16 bits.
397              
398             =head2 vendor
399              
400             Will byte-encode a complex vendorspec datastructure.
401              
402             =head2 vendorspec
403              
404             Will byte-encode a complex vendorspec datastructure.
405              
406             =head1 SEE ALSO
407              
408             L
409              
410             =cut