File Coverage

blib/lib/DOCSIS/ConfigFile.pm
Criterion Covered Total %
statement 149 153 97.3
branch 60 70 85.7
condition 21 26 80.7
subroutine 19 19 100.0
pod 2 2 100.0
total 251 270 92.9


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