File Coverage

blib/lib/DOCSIS/ConfigFile.pm
Criterion Covered Total %
statement 144 148 97.3
branch 59 68 86.7
condition 21 26 80.7
subroutine 18 18 100.0
pod 2 2 100.0
total 244 262 93.1


line stmt bran cond sub pod time code
1             package DOCSIS::ConfigFile;
2 17     17   1002756 use strict;
  17         192  
  17         553  
3 17     17   98 use warnings;
  17         39  
  17         816  
4              
5 17   50 17   100 use constant CAN_TRANSLATE_OID => $ENV{DOCSIS_CAN_TRANSLATE_OID} // eval 'require SNMP;1' || 0;
  17         36  
  17         1687  
6 17   50 17   121 use constant DEBUG => $ENV{DOCSIS_CONFIGFILE_DEBUG} || 0;
  17         38  
  17         1088  
7 17     17   11247 use if DEBUG, 'Data::Dumper';
  17         234  
  17         93  
8              
9             if (CAN_TRANSLATE_OID) {
10             require File::Basename;
11             require File::Spec;
12             our $OID_DIR = File::Spec->rel2abs(
13             File::Spec->catdir(File::Basename::dirname(__FILE__), 'ConfigFile', 'mibs'));
14             warn "[DOCSIS] Adding OID directory $OID_DIR\n" if DEBUG;
15             SNMP::addMibDirs($OID_DIR);
16             SNMP::loadModules('ALL');
17             }
18              
19 17     17   2630 use Digest::MD5 ();
  17         33  
  17         294  
20 17     17   7780 use Digest::HMAC_MD5;
  17         24887  
  17         719  
21 17     17   8892 use Digest::SHA;
  17         53186  
  17         937  
22 17     17   128 use Exporter 'import';
  17         41  
  17         576  
23 17     17   7972 use DOCSIS::ConfigFile::Decode;
  17         61  
  17         742  
24 17     17   8856 use DOCSIS::ConfigFile::Encode;
  17         51  
  17         17656  
25              
26             our $VERSION = '0.77';
27             our @EXPORT_OK = qw(decode_docsis encode_docsis);
28             our ($DEPTH, $CONFIG_TREE, @CMTS_MIC) = (0, {});
29              
30             sub decode_docsis {
31 50 100   50 1 5839 my $args = ref $_[-1] eq 'HASH' ? $_[-1] : {};
32 50         100 my $bytes = shift;
33 50   66     168 my $current = $args->{blueprint} || $CONFIG_TREE;
34 50   100     141 my $pos = $args->{pos} || 0;
35 50         92 my $data = {};
36 50         77 my $end;
37              
38 50 100       131 if (ref $bytes eq 'SCALAR') {
39 3         10 my ($file, $r) = ($$bytes, 0);
40 3         6 $bytes = '';
41 3 50       134 open my $BYTES, '<', $file or die "decode_docsis $file: $!";
42 3         110 while ($r = sysread $BYTES, my $buf, 131072, 0) { $bytes .= $buf }
  3         37  
43 3 50       48 die "decode_docsis $file: $!" unless defined $r;
44             }
45              
46 50         72 local $DEPTH = $DEPTH + 1 if DEBUG;
47 50   66     159 $end = $args->{end} || length $bytes;
48              
49 50         126 while ($pos < $end) {
50 266 100       828 my $code = unpack 'C', substr $bytes, $pos++, 1 or next; # next on $code=0
51 231         369 my ($length, $t, $name, $syminfo, $value);
52              
53 231         1013 for (keys %$current) {
54 2821 100       5155 next unless $code == $current->{$_}{code};
55 231         365 $name = $_;
56 231         326 $syminfo = $current->{$_};
57 231         315 last;
58             }
59              
60 231 50       592 unless ($name) {
61 0         0 warn "[DOCSIS] Internal error: No syminfo defined for code=$code.";
62 0         0 next;
63             }
64              
65 231 100       454 unless ($syminfo->{lsize}) {
66 16         43 warn sprintf "[DOCSIS]%sDecode %s type=%s (0x%02x), len=0\n", join('', (' ' x $DEPTH)),
67             $name, $code, $code
68             if DEBUG;
69 16         42 next;
70             }
71              
72             # Document: PKT-SP-PROV1.5-I03-070412
73             # Chapter: 9.1 MTA Configuration File
74 215 50       415 $t = $syminfo->{lsize} == 1 ? 'C' : 'n'; # 1=C, 2=n
75 215         449 $length = unpack $t, substr $bytes, $pos, $syminfo->{lsize};
76 215         357 $pos += $syminfo->{lsize};
77              
78             warn sprintf "[DOCSIS]%sDecode %s type=%s (0x%02x), len=%s, with %s()\n",
79 215         262 join('', (' ' x $DEPTH)), $name, $code, $code, $length, $syminfo->{func} // 'unknown'
80             if DEBUG;
81              
82 215 100       980 if ($syminfo->{nested}) {
    50          
83 31         130 local @$args{qw(blueprint end pos)} = ($syminfo->{nested}, $length + $pos, $pos);
84 31         156 $value = decode_docsis($bytes, $args);
85             }
86             elsif (my $f = DOCSIS::ConfigFile::Decode->can($syminfo->{func})) {
87 184         601 $value = $f->(substr $bytes, $pos, $length);
88 184 100       507 $value = {oid => @$value{qw(oid type value)}} if $name eq 'SnmpMibObject';
89             }
90             else {
91 0         0 die
92             qq(Can't locate object method "$syminfo->{func}" via package "DOCSIS::ConfigFile::Decode");
93             }
94              
95 215         338 $pos += $length;
96              
97 215 100       444 if (!exists $data->{$name}) {
    100          
98 201         539 $data->{$name} = $value;
99             }
100             elsif (ref $data->{$name} eq 'ARRAY') {
101 4         8 push @{$data->{$name}}, $value;
  4         14  
102             }
103             else {
104 10         40 $data->{$name} = [$data->{$name}, $value];
105             }
106             }
107              
108 50         185 return $data;
109             }
110              
111             sub encode_docsis {
112 80     80 1 11376 my ($data, $args) = @_;
113 80   66     279 my $current = $args->{blueprint} || $CONFIG_TREE;
114 80         134 my $mic = {};
115 80         149 my $bytes = '';
116              
117 80   100     272 local $args->{depth} = ($args->{depth} || 0) + 1;
118 80         114 local $DEPTH = $args->{depth} if DEBUG;
119              
120 80 100 100     272 if ($args->{depth} == 1 and defined $args->{mta_algorithm}) {
121 3         6 delete $data->{MtaConfigDelimiter};
122 3         16 $bytes .= encode_docsis({MtaConfigDelimiter => 1}, {depth => 1});
123             }
124              
125 80         514 for my $name (sort { $current->{$a}{code} <=> $current->{$b}{code} } keys %$current) {
  5223         8136  
126 1383 100       2478 next unless defined $data->{$name};
127 213         319 my $syminfo = $current->{$name};
128 213         334 my ($type, $length, $value);
129              
130 213         427 for my $item (_to_list($data->{$name}, $syminfo)) {
131 243 100       1141 if ($syminfo->{nested}) {
    50          
132 50         67 warn "[DOCSIS]@{[' 'x$DEPTH]}Encode $name with encode_docsis\n" if DEBUG;
133 50         141 local @$args{qw(blueprint)} = ($current->{$name}{nested});
134 50         173 $value = encode_docsis($item, $args);
135             }
136             elsif (my $f = DOCSIS::ConfigFile::Encode->can($syminfo->{func})) {
137 193         275 warn "[DOCSIS]@{[' 'x$DEPTH]}Encode $name with $syminfo->{func}\n" if DEBUG;
138 193 100       479 if ($syminfo->{func} =~ /_list$/) {
    100          
139 1         5 $value = pack 'C*', $f->({value => _validate($item, $syminfo)});
140             }
141             elsif ($name eq 'SnmpMibObject') {
142 10         27 my @k = qw(type value);
143 10         36 local $item->{oid} = $item->{oid};
144             $value = pack 'C*',
145 10         54 $f->({value => {oid => delete $item->{oid}, map { shift(@k), $_ } %$item}});
  20         122  
146             }
147             else {
148 182         396 local $syminfo->{name} = $name;
149 182         863 $value = pack 'C*', $f->({value => _validate($item, $syminfo)});
150             }
151             }
152             else {
153 0         0 die
154             qq(Can't locate object method "$syminfo->{func}" via package "DOCSIS::ConfigFile::Encode");
155             }
156              
157             {
158 17     17   152 use warnings FATAL => 'all';
  17         40  
  17         41613  
  241         536  
159 241         467 $type = pack 'C', $syminfo->{code};
160 241 50       595 $length = $syminfo->{lsize} == 2 ? pack('n', length $value) : pack('C', length $value);
161             }
162              
163 240         577 $mic->{$name} = "$type$length$value";
164 240         581 $bytes .= $mic->{$name};
165             }
166             }
167              
168 77 100       352 return $bytes if $args->{depth} != 1;
169 19 100       115 return _mta_eof($bytes, $args) if defined $args->{mta_algorithm};
170 16         72 return _cm_eof($bytes, $mic, $args);
171             }
172              
173             sub _cm_eof {
174 16     16   35 my $mic = $_[1];
175 16         33 my $args = $_[2];
176 16         31 my $cmts_mic = '';
177 16         58 my $pads = 4 - (1 + length $_[0]) % 4;
178 16         29 my $eod_pad;
179              
180 16         113 $mic->{CmMic} = pack('C*', 6, 16) . Digest::MD5::md5($_[0]);
181              
182 16   100     508 $cmts_mic .= $mic->{$_} || '' for @CMTS_MIC;
183             $cmts_mic
184 16   100     135 = pack('C*', 7, 16) . Digest::HMAC_MD5::hmac_md5($cmts_mic, $args->{shared_secret} || '');
185 16         436 $eod_pad = pack('C', 255) . ("\0" x $pads);
186              
187 16         110 return $_[0] . $mic->{CmMic} . $cmts_mic . $eod_pad;
188             }
189              
190             sub _mta_eof {
191 3   100 3   12 my $mta_algorithm = $_[1]->{mta_algorithm} || '';
192 3         7 my $hash = '';
193              
194 3 100       6 if ($mta_algorithm) {
195 2 100       31 $hash = $mta_algorithm eq 'md5' ? Digest::MD5::md5_hex($_[0]) : Digest::SHA::sha1_hex($_[0]);
196 2         17 $hash
197             = encode_docsis(
198             {SnmpMibObject => {oid => '1.3.6.1.4.1.4491.2.2.1.1.2.7.0', STRING => "0x$hash"}},
199             {depth => 1});
200             }
201              
202 3         17 return $hash . $_[0] . encode_docsis({MtaConfigDelimiter => 255}, {depth => 1});
203             }
204              
205             sub _to_list {
206 213 100   213   597 return $_[0] if $_[1]->{func} =~ /_list$/;
207 212 100       433 return @{$_[0]} if ref $_[0] eq 'ARRAY';
  6         17  
208 206         398 return $_[0];
209             }
210              
211             # _validate($value, $syminfo);
212             sub _validate {
213 183 100   183   463 if ($_[1]->{limit}[1]) {
214 148 100       623 if ($_[0] =~ /^-?\d+$/) {
215 143 100       361 die "[DOCSIS] $_[1]->{name} holds a too high value. ($_[0])" if $_[1]->{limit}[1] < $_[0];
216 142 100       298 die "[DOCSIS] $_[1]->{name} holds a too low value. ($_[0])" if $_[0] < $_[1]->{limit}[0];
217             }
218             else {
219 5 100       19 my $length = ref $_[0] eq 'ARRAY' ? @{$_[0]} : length $_[0];
  1         3  
220 5 50       18 die "[DOCSIS] $_[1]->{name} is too long. ($_[0])" if $_[1]->{limit}[1] < $length;
221 5 50       25 die "[DOCSIS] $_[1]->{name} is too short. ($_[0])" if $length < $_[1]->{limit}[0];
222             }
223             }
224 181         623 return $_[0];
225             }
226              
227             @CMTS_MIC = qw(
228             DownstreamFrequency UpstreamChannelId NetworkAccess
229             ClassOfService BaselinePrivacy VendorSpecific
230             CmMic MaxCPE TftpTimestamp
231             TftpModemAddress UsPacketClass DsPacketClass
232             UsServiceFlow DsServiceFlow MaxClassifiers
233             GlobalPrivacyEnable PHS SubMgmtControl
234             SubMgmtCpeTable SubMgmtFilters TestMode
235             );
236              
237             $CONFIG_TREE = {
238             BaselinePrivacy => {
239             code => 17,
240             func => 'nested',
241             lsize => 1,
242             limit => [0, 0],
243             nested => {
244             AuthGraceTime => {code => 3, func => 'uint', lsize => 1, limit => [1, 6047999]},
245             AuthRejectTimeout => {code => 7, func => 'uint', lsize => 1, limit => [1, 600]},
246             AuthTimeout => {code => 1, func => 'uint', lsize => 1, limit => [1, 30]},
247             OperTimeout => {code => 4, func => 'uint', lsize => 1, limit => [1, 10]},
248             ReAuthTimeout => {code => 2, func => 'uint', lsize => 1, limit => [1, 30]},
249             ReKeyTimeout => {code => 5, func => 'uint', lsize => 1, limit => [1, 10]},
250             SAMapMaxRetries => {code => 9, func => 'uint', lsize => 1, limit => [0, 10]},
251             SAMapWaitTimeout => {code => 8, func => 'uint', lsize => 1, limit => [1, 10]},
252             TEKGraceTime => {code => 6, func => 'uint', lsize => 1, limit => [1, 302399]},
253             },
254             },
255             ClassOfService => {
256             code => 4,
257             func => 'nested',
258             lsize => 1,
259             limit => [0, 0],
260             nested => {
261             ClassID => {code => 1, func => 'uchar', lsize => 1, limit => [1, 16]},
262             GuaranteedUp => {code => 5, func => 'uint', lsize => 1, limit => [0, 10000000]},
263             MaxBurstUp => {code => 6, func => 'ushort', lsize => 1, limit => [0, 65535]},
264             MaxRateDown => {code => 2, func => 'uint', lsize => 1, limit => [0, 52000000]},
265             MaxRateUp => {code => 3, func => 'uint', lsize => 1, limit => [0, 10000000]},
266             PriorityUp => {code => 4, func => 'uchar', lsize => 1, limit => [0, 7]},
267             PrivacyEnable => {code => 7, func => 'uchar', lsize => 1, limit => [0, 1]},
268             },
269             },
270             CmMic => {code => 6, func => 'mic', lsize => 1, limit => [0, 0]},
271             CmtsMic => {code => 7, func => 'mic', lsize => 1, limit => [0, 0]},
272             CpeMacAddress => {code => 14, func => 'ether', lsize => 1, limit => [0, 0]},
273             DocsisTwoEnable => {code => 39, func => 'uchar', lsize => 1, limit => [0, 1]},
274             DownstreamFrequency => {code => 1, func => 'uint', lsize => 1, limit => [88000000, 860000000]},
275             DsChannelList => {
276             code => 41,
277             func => 'nested',
278             lsize => 1,
279             limit => [1, 255],
280             nested => {
281             DefaultScanTimeout => {code => 3, func => 'ushort', lsize => 1, limit => [0, 65535]},
282             DsFreqRange => {
283             code => 2,
284             func => 'nested',
285             lsize => 1,
286             limit => [1, 255],
287             nested => {
288             DsFreqRangeEnd => {code => 3, func => 'uint', lsize => 1, limit => [0, 4294967295]},
289             DsFreqRangeStart => {code => 2, func => 'uint', lsize => 1, limit => [0, 4294967295]},
290             DsFreqRangeStepSize => {code => 4, func => 'uint', lsize => 1, limit => [0, 4294967295]},
291             DsFreqRangeTimeout => {code => 1, func => 'ushort', lsize => 1, limit => [0, 65535]},
292             },
293             },
294             SingleDsChannel => {
295             code => 1,
296             func => 'nested',
297             lsize => 1,
298             limit => [1, 255],
299             nested => {
300             SingleDsFrequency => {code => 2, func => 'uint', lsize => 1, limit => [0, 4294967295]},
301             SingleDsTimeout => {code => 1, func => 'ushort', lsize => 1, limit => [0, 65535]},
302             },
303             },
304             },
305             },
306             DsPacketClass => {
307             code => 23,
308             func => 'nested',
309             lsize => 1,
310             limit => [0, 0],
311             nested => {
312             ActivationState => {code => 6, func => 'uchar', lsize => 1, limit => [0, 1]},
313             ClassifierId => {code => 2, func => 'ushort', lsize => 1, limit => [1, 65535]},
314             ClassifierRef => {code => 1, func => 'uchar', lsize => 1, limit => [1, 255]},
315             DscAction => {code => 7, func => 'uchar', lsize => 1, limit => [0, 2]},
316             IEEE802Classifier => {
317             code => 11,
318             func => 'nested',
319             lsize => 1,
320             limit => [0, 0],
321             nested => {
322             UserPriority => {code => 1, func => 'ushort', lsize => 1, limit => [0, 0]},
323             VlanID => {code => 2, func => 'ushort', lsize => 1, limit => [0, 0]},
324             },
325             },
326             IpPacketClassifier => {
327             code => 9,
328             func => 'nested',
329             lsize => 1,
330             limit => [0, 0],
331             nested => {
332             DstPortEnd => {code => 10, func => 'ushort', lsize => 1, limit => [0, 65535]},
333             DstPortStart => {code => 9, func => 'ushort', lsize => 1, limit => [0, 65535]},
334             IpDstAddr => {code => 5, func => 'ip', lsize => 1, limit => [0, 0]},
335             IpDstMask => {code => 6, func => 'ip', lsize => 1, limit => [0, 0]},
336             IpProto => {code => 2, func => 'ushort', lsize => 1, limit => [0, 257]},
337             IpSrcAddr => {code => 3, func => 'ip', lsize => 1, limit => [0, 0]},
338             IpSrcMask => {code => 4, func => 'ip', lsize => 1, limit => [0, 0]},
339             IpTos => {code => 1, func => 'hexstr', lsize => 1, limit => [0, 0]},
340             SrcPortEnd => {code => 8, func => 'ushort', lsize => 1, limit => [0, 65535]},
341             SrcPortStart => {code => 7, func => 'ushort', lsize => 1, limit => [0, 65535]},
342             },
343             },
344             LLCPacketClassifier => {
345             code => 10,
346             func => 'nested',
347             lsize => 1,
348             limit => [0, 0],
349             nested => {
350             DstMacAddress => {code => 1, func => 'ether', lsize => 1, limit => [0, 0]},
351             EtherType => {code => 3, func => 'hexstr', lsize => 1, limit => [0, 0]},
352             SrcMacAddress => {code => 2, func => 'ether', lsize => 1, limit => [0, 0]},
353             },
354             },
355             RulePriority => {code => 5, func => 'uchar', lsize => 1, limit => [0, 255]},
356             ServiceFlowId => {code => 4, func => 'uint', lsize => 1, limit => [1, 4294967295]},
357             ServiceFlowRef => {code => 3, func => 'ushort', lsize => 1, limit => [1, 65535]},
358             },
359             },
360             DsServiceFlow => {
361             code => 25,
362             func => 'nested',
363             lsize => 1,
364             limit => [0, 0],
365             nested => {
366             ActQosParamsTimeout => {code => 12, func => 'ushort', lsize => 1, limit => [0, 65535]},
367             AdmQosParamsTimeout => {code => 13, func => 'ushort', lsize => 1, limit => [0, 65535]},
368             DsServiceFlowId => {code => 2, func => 'uint', lsize => 1, limit => [1, 4294967295]},
369             DsServiceFlowRef => {code => 1, func => 'ushort', lsize => 1, limit => [1, 65535]},
370             DsVendorSpecific => {code => 43, func => 'vendor', lsize => 1, limit => [0, 0]},
371             MaxDsLatency => {code => 14, func => 'uint', lsize => 1, limit => [0, 0]},
372             MaxRateSustained => {code => 8, func => 'uint', lsize => 1, limit => [0, 4294967295]},
373             MaxTrafficBurst => {code => 9, func => 'uint', lsize => 1, limit => [0, 4294967295]},
374             MinReservedRate => {code => 10, func => 'uint', lsize => 1, limit => [0, 4294967295]},
375             MinResPacketSize => {code => 11, func => 'ushort', lsize => 1, limit => [0, 65535]},
376             QosParamSetType => {code => 6, func => 'uchar', lsize => 1, limit => [0, 255]},
377             ServiceClassName => {code => 4, func => 'stringz', lsize => 1, limit => [2, 16]},
378             TrafficPriority => {code => 7, func => 'uchar', lsize => 1, limit => [0, 7]},
379             },
380             },
381             GenericTLV => {code => 255, func => 'no_value', lsize => 0, limit => [0, 0]},
382             GlobalPrivacyEnable => {code => 29, func => 'uchar', lsize => 1, limit => [0, 0]},
383             MaxClassifiers => {code => 28, func => 'ushort', lsize => 1, limit => [0, 0]},
384             MaxCPE => {code => 18, func => 'uchar', lsize => 1, limit => [1, 254]},
385             MfgCVCData => {code => 32, func => 'hexstr', lsize => 1, limit => [0, 0]},
386             ModemCapabilities => {
387             code => 5,
388             func => 'nested',
389             lsize => 1,
390             limit => [0, 0],
391             nested => {
392             BaselinePrivacySupport => {code => 6, func => 'uchar', lsize => 1, limit => [0, 1]},
393             ConcatenationSupport => {code => 1, func => 'uchar', lsize => 1, limit => [0, 1]},
394             DCCSupport => {code => 12, func => 'uchar', lsize => 1, limit => [0, 1]},
395             DownstreamSAIDSupport => {code => 7, func => 'uchar', lsize => 1, limit => [0, 255]},
396             FragmentationSupport => {code => 3, func => 'uchar', lsize => 1, limit => [0, 1]},
397             IGMPSupport => {code => 5, func => 'uchar', lsize => 1, limit => [0, 1]},
398             ModemDocsisVersion => {code => 2, func => 'uchar', lsize => 1, limit => [0, 2]},
399             PHSSupport => {code => 4, func => 'uchar', lsize => 1, limit => [0, 1]},
400             UpstreamSIDSupport => {code => 8, func => 'uchar', lsize => 1, limit => [0, 255]},
401             },
402             },
403             MtaConfigDelimiter => {code => 254, func => 'uchar', lsize => 1, limit => [1, 255]},
404             NetworkAccess => {code => 3, func => 'uchar', lsize => 1, limit => [0, 1]},
405             PHS => {
406             code => 26,
407             func => 'nested',
408             lsize => 1,
409             limit => [0, 0],
410             nested => {
411             PHSClassifierId => {code => 2, func => 'ushort', lsize => 1, limit => [1, 65535]},
412             PHSClassifierRef => {code => 1, func => 'uchar', lsize => 1, limit => [1, 255]},
413             PHSField => {code => 7, func => 'hexstr', lsize => 1, limit => [1, 255]},
414             PHSIndex => {code => 8, func => 'uchar', lsize => 1, limit => [1, 255]},
415             PHSMask => {code => 9, func => 'hexstr', lsize => 1, limit => [1, 255]},
416             PHSServiceFlowId => {code => 4, func => 'uint', lsize => 1, limit => [1, 4294967295]},
417             PHSServiceFlowRef => {code => 3, func => 'ushort', lsize => 1, limit => [1, 65535]},
418             PHSSize => {code => 10, func => 'uchar', lsize => 1, limit => [1, 255]},
419             PHSVerify => {code => 11, func => 'uchar', lsize => 1, limit => [0, 1]},
420             },
421             },
422             SnmpCpeAccessControl => {code => 55, func => 'uchar', lsize => 1, limit => [0, 1]},
423             SnmpMibObject => {code => 11, func => 'snmp_object', lsize => 1, limit => [1, 255]},
424             SnmpV3Kickstart => {
425             code => 34,
426             func => 'nested',
427             lsize => 1,
428             limit => [0, 0],
429             nested => {
430             SnmpV3MgrPublicNumber => {code => 2, func => 'hexstr', lsize => 1, limit => [1, 514]},
431             SnmpV3SecurityName => {code => 1, func => 'string', lsize => 1, limit => [1, 16]},
432             },
433             },
434             SnmpV3TrapReceiver => {
435             code => 38,
436             func => 'nested',
437             lsize => 1,
438             limit => [0, 0],
439             nested => {
440             SnmpV3TrapRxFilterOID => {code => 6, func => 'ushort', lsize => 1, limit => [1, 5]},
441             SnmpV3TrapRxIP => {code => 1, func => 'ip', lsize => 1, limit => [0, 0]},
442             SnmpV3TrapRxPort => {code => 2, func => 'ushort', lsize => 1, limit => [0, 0]},
443             SnmpV3TrapRxRetries => {code => 5, func => 'ushort', lsize => 1, limit => [0, 65535]},
444             SnmpV3TrapRxSecurityName => {code => 7, func => 'string', lsize => 1, limit => [1, 16]},
445             SnmpV3TrapRxTimeout => {code => 4, func => 'ushort', lsize => 1, limit => [0, 65535]},
446             SnmpV3TrapRxType => {code => 3, func => 'ushort', lsize => 1, limit => [1, 5]},
447             },
448             },
449             SubMgmtControl => {code => 35, func => 'hexstr', lsize => 1, limit => [3, 3]},
450             SubMgmtCpeTable => {code => 36, func => 'hexstr', lsize => 1, limit => [0, 0]},
451             SubMgmtFilters => {code => 37, func => 'ushort_list', lsize => 1, limit => [0, 20]},
452             SwUpgradeFilename => {code => 9, func => 'string', lsize => 1, limit => [0, 0]},
453             SwUpgradeServer => {code => 21, func => 'ip', lsize => 1, limit => [0, 0]},
454             TestMode => {code => 40, func => 'hexstr', lsize => 1, limit => [0, 1]},
455             TftpModemAddress => {code => 20, func => 'ip', lsize => 1, limit => [0, 0]},
456             TftpTimestamp => {code => 19, func => 'uint', lsize => 1, limit => [0, 4294967295]},
457             UpstreamChannelId => {code => 2, func => 'uchar', lsize => 1, limit => [0, 255]},
458             UsPacketClass => {
459             code => 22,
460             func => 'nested',
461             lsize => 1,
462             limit => [0, 0],
463             nested => {
464             ActivationState => {code => 6, func => 'uchar', lsize => 1, limit => [0, 1]},
465             ClassifierId => {code => 2, func => 'ushort', lsize => 1, limit => [1, 65535]},
466             ClassifierRef => {code => 1, func => 'uchar', lsize => 1, limit => [1, 255]},
467             DscAction => {code => 7, func => 'uchar', lsize => 1, limit => [0, 2]},
468             IEEE802Classifier => {
469             code => 11,
470             func => 'nested',
471             lsize => 1,
472             limit => [0, 0],
473             nested => {
474             UserPriority => {code => 1, func => 'ushort', lsize => 1, limit => [0, 0]},
475             VlanID => {code => 2, func => 'ushort', lsize => 1, limit => [0, 0]},
476             },
477             },
478             IpPacketClassifier => {
479             code => 9,
480             func => 'nested',
481             lsize => 1,
482             limit => [0, 0],
483             nested => {
484             DstPortEnd => {code => 10, func => 'ushort', lsize => 1, limit => [0, 65535]},
485             DstPortStart => {code => 9, func => 'ushort', lsize => 1, limit => [0, 65535]},
486             IpDstAddr => {code => 5, func => 'ip', lsize => 1, limit => [0, 0]},
487             IpDstMask => {code => 6, func => 'ip', lsize => 1, limit => [0, 0]},
488             IpProto => {code => 2, func => 'ushort', lsize => 1, limit => [0, 257]},
489             IpSrcAddr => {code => 3, func => 'ip', lsize => 1, limit => [0, 0]},
490             IpSrcMask => {code => 4, func => 'ip', lsize => 1, limit => [0, 0]},
491             IpTos => {code => 1, func => 'hexstr', lsize => 1, limit => [0, 0]},
492             SrcPortEnd => {code => 8, func => 'ushort', lsize => 1, limit => [0, 65535]},
493             SrcPortStart => {code => 7, func => 'ushort', lsize => 1, limit => [0, 65535]},
494             }
495             },
496             LLCPacketClassifier => {
497             code => 10,
498             func => 'nested',
499             lsize => 1,
500             limit => [0, 0],
501             nested => {
502             DstMacAddress => {code => 1, func => 'ether', lsize => 1, limit => [0, 0]},
503             EtherType => {code => 3, func => 'hexstr', lsize => 1, limit => [0, 0]},
504             SrcMacAddress => {code => 2, func => 'ether', lsize => 1, limit => [0, 0]},
505             },
506             },
507             RulePriority => {code => 5, func => 'uchar', lsize => 1, limit => [0, 255]},
508             ServiceFlowId => {code => 4, func => 'uint', lsize => 1, limit => [1, 4294967295]},
509             ServiceFlowRef => {code => 3, func => 'ushort', lsize => 1, limit => [1, 65535]},
510             },
511             },
512             UsServiceFlow => {
513             code => 24,
514             func => 'nested',
515             lsize => 1,
516             limit => [0, 0],
517             nested => {
518             ActQosParamsTimeout => {code => 12, func => 'ushort', lsize => 1, limit => [0, 65535]},
519             AdmQosParamsTimeout => {code => 13, func => 'ushort', lsize => 1, limit => [0, 65535]},
520             GrantsPerInterval => {code => 22, func => 'uchar', lsize => 1, limit => [0, 127]},
521             IpTosOverwrite => {code => 23, func => 'hexstr', lsize => 1, limit => [0, 255]},
522             MaxConcatenatedBurst => {code => 14, func => 'ushort', lsize => 1, limit => [0, 65535]},
523             MaxRateSustained => {code => 8, func => 'uint', lsize => 1, limit => [0, 0]},
524             MaxTrafficBurst => {code => 9, func => 'uint', lsize => 1, limit => [0, 0]},
525             MinReservedRate => {code => 10, func => 'uint', lsize => 1, limit => [0, 0]},
526             MinResPacketSize => {code => 11, func => 'ushort', lsize => 1, limit => [0, 65535]},
527             NominalGrantInterval => {code => 20, func => 'uint', lsize => 1, limit => [0, 0]},
528             NominalPollInterval => {code => 17, func => 'uint', lsize => 1, limit => [0, 0]},
529             QosParamSetType => {code => 6, func => 'uchar', lsize => 1, limit => [0, 255]},
530             RequestOrTxPolicy => {code => 16, func => 'hexstr', lsize => 1, limit => [0, 255]},
531             SchedulingType => {code => 15, func => 'uchar', lsize => 1, limit => [0, 6]},
532             ServiceClassName => {code => 4, func => 'stringz', lsize => 1, limit => [2, 16]},
533             ToleratedGrantJitter => {code => 21, func => 'uint', lsize => 1, limit => [0, 0]},
534             ToleratedPollJitter => {code => 18, func => 'uint', lsize => 1, limit => [0, 0]},
535             TrafficPriority => {code => 7, func => 'uchar', lsize => 1, limit => [0, 7]},
536             UnsolicitedGrantSize => {code => 19, func => 'ushort', lsize => 1, limit => [0, 65535]},
537             UsServiceFlowId => {code => 2, func => 'uint', lsize => 1, limit => [1, 4294967295]},
538             UsServiceFlowRef => {code => 1, func => 'ushort', lsize => 1, limit => [1, 65535]},
539             UsVendorSpecific => {code => 43, func => 'vendor', lsize => 1, limit => [0, 0]},
540             },
541             },
542             eRouter => {
543             code => 202,
544             func => 'nested',
545             lsize => 1,
546             limit => [0, 0],
547             nested => {
548             InitializationMode => {code => 1, func => 'uchar', lsize => 1, limit => [0, 3]},
549             ManagementServer => {
550             code => 2,
551             func => 'nested',
552             lsize => 1,
553             limit => [0, 0],
554             nested => {
555             EnableCWMP => {code => 1, func => 'uchar', lsize => 1, limit => [0, 1]},
556             URL => {code => 2, func => 'string', lsize => 1, limit => [0, 0]},
557             Username => {code => 3, func => 'string', lsize => 1, limit => [0, 0]},
558             Password => {code => 4, func => 'string', lsize => 1, limit => [0, 0]},
559             ConnectionRequestUsername => {code => 5, func => 'string', lsize => 1, limit => [0, 0]},
560             ConnectionRequestPassword => {code => 6, func => 'string', lsize => 1, limit => [0, 0]},
561             ACSOverride => {code => 7, func => 'uchar', lsize => 1, limit => [0, 1]},
562             },
563             },
564             InitializationModeOverride => {code => 3, func => 'uchar', lsize => 1, limit => [0, 1]},
565             },
566             },
567             VendorSpecific => {code => 43, func => 'vendor', lsize => 1, limit => [0, 0]},
568             };
569              
570             =encoding utf8
571              
572             =head1 NAME
573              
574             DOCSIS::ConfigFile - Decodes and encodes DOCSIS config files
575              
576             =head1 DESCRIPTION
577              
578             L is a class which provides functionality to decode and
579             encode L (Data over Cable Service Interface
580             Specifications) config files.
581              
582             This module is used as a layer between any human readable data and
583             the binary structure.
584              
585             The files are usually served using a L, after a
586             L or MTA (Multimedia
587             Terminal Adapter) has recevied an IP address from a L
588             server. These files are L using a
589             variety of functions, but all the data in the file are constructed by TLVs
590             (type-length-value) blocks. These can be nested and concatenated.
591              
592             See the source code or L for list of
593             supported parameters.
594              
595             =head1 SYNOPSIS
596              
597             use DOCSIS::ConfigFile qw(encode_docsis decode_docsis);
598              
599             $data = decode_docsis $bytes;
600              
601             $bytes = encode_docsis(
602             {
603             GlobalPrivacyEnable => 1,
604             MaxCPE => 2,
605             NetworkAccess => 1,
606             BaselinePrivacy => {
607             AuthTimeout => 10,
608             ReAuthTimeout => 10,
609             AuthGraceTime => 600,
610             OperTimeout => 1,
611             ReKeyTimeout => 1,
612             TEKGraceTime => 600,
613             AuthRejectTimeout => 60,
614             SAMapWaitTimeout => 1,
615             SAMapMaxRetries => 4
616             },
617             SnmpMibObject => [
618             {oid => "1.3.6.1.4.1.1.77.1.6.1.1.6.2", INTEGER => 1},
619             {oid => "1.3.6.1.4.1.1429.77.1.6.1.1.6.2", STRING => "bootfile.bin"}
620             ],
621             VendorSpecific => {
622             id => "0x0011ee",
623             options => [30 => "0xff", 31 => "0x00", 32 => "0x28"]
624             }
625             }
626             );
627              
628             =head1 OPTIONAL MODULE
629              
630             You can install the L module to translate between SNMP
631             OID formats. With the module installed, you can define the C
632             like the example below, instead of using numeric OIDs:
633              
634             encode_docsis(
635             {
636             SnmpMibObject => [
637             {oid => "docsDevNmAccessIp.1", IPADDRESS => "10.0.0.1"},
638             {oid => "docsDevNmAccessIpMask.1", IPADDRESS => "255.255.255.255"},
639             ]
640             },
641             );
642              
643             =head1 WEB APPLICATION
644              
645             There is an example web application bundled with this distribution called
646             "Docsisious". To run this application, you need to install L and
647             L:
648              
649             $ curl -L https://cpanmin.us \
650             | perl - -M https://cpan.metacpan.org \
651             DOCSIS::ConfigFile \
652             Mojolicious \
653             YAML::XS
654              
655             After installing the modules above, you can run the web app like this:
656              
657             $ docsisious --listen http://*:8000
658              
659             And then open your favorite browser at L. To see a live
660             demo, you can visit L.
661              
662             =head1 FUNCTIONS
663              
664             =head2 decode_docsis
665              
666             $data = decode_docsis($byte_string);
667             $data = decode_docsis(\$path_to_file);
668              
669             Used to decode a DOCSIS config file into a data structure. The output
670             C<$data> can be used as input to L. Note: C<$data>
671             will only contain array-refs if the DOCSIS parameter occur more than
672             once.
673              
674             =head2 encode_docsis
675              
676             $byte_string = encode_docsis(\%data, \%args);
677              
678             Used to encode a data structure into a DOCSIS config file. Each of the keys
679             in C<$data> can either hold a hash- or array-ref. An array-ref is used if
680             the same DOCSIS parameter occur multiple times. These two formats will result
681             in the same C<$byte_string>:
682              
683             # Only one SnmpMibObject
684             encode_docsis({
685             SnmpMibObject => {
686             oid => "1.3.6.1.4.1.1429.77.1.6.1.1.6.2", STRING => "bootfile.bin"
687             }
688             })
689              
690             # Allow one or more SnmpMibObjects
691             encode_docsis({
692             SnmpMibObject => [
693             {oid => "1.3.6.1.4.1.1429.77.1.6.1.1.6.2", STRING => "bootfile.bin"}
694             ]
695             })
696              
697             Possible C<%args>:
698              
699             =over 4
700              
701             =item * mta_algorithm
702              
703             This argument is required when encoding MTA config files. Can be set to
704             either empty string, "sha1" or "md5".
705              
706             =item * shared_secret
707              
708             This argument is optional, but will be used as the shared secret used to
709             increase security between the cable modem and CMTS.
710              
711             =back
712              
713             =head1 COPYRIGHT AND LICENSE
714              
715             Copyright (C) 2014-2018, Jan Henning Thorsen
716              
717             This program is free software; you can redistribute it and/or modify it
718             under the same terms as Perl itself.
719              
720             =head1 AUTHOR
721              
722             Jan Henning Thorsen - C
723              
724             =cut