package DOCSIS::ConfigFile;
use strict;
use warnings;
use Carp qw(carp confess croak);
use constant CAN_TRANSLATE_OID => $ENV{DOCSIS_CAN_TRANSLATE_OID} // eval 'require SNMP;1' || 0;
use constant DEBUG => $ENV{DOCSIS_CONFIGFILE_DEBUG} || 0;
use if DEBUG, 'Data::Dumper';
if (CAN_TRANSLATE_OID) {
require File::Basename;
require File::Spec;
our $OID_DIR = File::Spec->rel2abs(
File::Spec->catdir(File::Basename::dirname(__FILE__), 'ConfigFile', 'mibs'));
warn "[DOCSIS] Adding OID directory $OID_DIR\n" if DEBUG;
SNMP::addMibDirs($OID_DIR);
SNMP::loadModules('ALL');
}
use Digest::MD5 ();
use Digest::HMAC_MD5;
use Digest::SHA;
use Exporter 'import';
use DOCSIS::ConfigFile::Decode;
use DOCSIS::ConfigFile::Encode;
our $VERSION = '1.01';
our @EXPORT_OK = qw(decode_docsis encode_docsis);
our ($DEPTH, $CONFIG_TREE, @CMTS_MIC) = (0, {});
sub decode_docsis {
my $options = ref $_[-1] eq 'HASH' ? $_[-1] : {};
my $bytes = shift;
my $current = $options->{blueprint} || $CONFIG_TREE;
my $pos = $options->{pos} || 0;
my $data = {};
my $end;
if (ref $bytes eq 'SCALAR') {
my ($file, $r) = ($$bytes, 0);
$bytes = '';
open my $BYTES, '<', $file or croak "Can't decode DOCSIS file $file: $!";
while ($r = sysread $BYTES, my $buf, 131072, 0) { $bytes .= $buf }
croak "Can't decode DOCSIS file $file: $!" unless defined $r;
warn "[DOCSIS] Decode @{[length $bytes]} bytes from $file\n" if DEBUG;
}
local $DEPTH = $DEPTH + 1 if DEBUG;
$end = $options->{end} || length $bytes;
while ($pos < $end) {
my $code = unpack 'C', substr $bytes, $pos++, 1 or next; # next on $code=0
my ($length, $t, $name, $syminfo, $value);
for (keys %$current) {
next unless $code == $current->{$_}{code};
$name = $_;
$syminfo = $current->{$_};
last;
}
unless ($name) {
carp "[DOCSIS] Internal error: No syminfo defined for code=$code.";
next;
}
unless ($syminfo->{lsize}) {
warn sprintf "[DOCSIS]%sDecode %s type=%s (0x%02x), len=0\n", join('', (' ' x $DEPTH)),
$name, $code, $code
if DEBUG;
next;
}
# Document: PKT-SP-PROV1.5-I03-070412
# Chapter: 9.1 MTA Configuration File
$t = $syminfo->{lsize} == 1 ? 'C' : 'n'; # 1=C, 2=n
$length = unpack $t, substr $bytes, $pos, $syminfo->{lsize};
$pos += $syminfo->{lsize};
warn sprintf "[DOCSIS]%sDecode %s type=%s (0x%02x), len=%s, with %s()\n",
join('', (' ' x $DEPTH)), $name, $code, $code, $length, $syminfo->{func} // 'unknown'
if DEBUG;
if ($syminfo->{nested}) {
local @$options{qw(blueprint end pos)} = ($syminfo->{nested}, $length + $pos, $pos);
$value = decode_docsis($bytes, $options);
}
elsif (my $f = DOCSIS::ConfigFile::Decode->can($syminfo->{func})) {
$value = $f->(substr $bytes, $pos, $length);
$value = {oid => @$value{qw(oid type value)}} if $name eq 'SnmpMibObject';
}
else {
confess
qq(Can't locate object method "$syminfo->{func}" via package "DOCSIS::ConfigFile::Decode");
}
$pos += $length;
if (!exists $data->{$name}) {
$data->{$name} = $value;
}
elsif (ref $data->{$name} eq 'ARRAY') {
push @{$data->{$name}}, $value;
}
else {
$data->{$name} = [$data->{$name}, $value];
}
}
return $data;
}
sub encode_docsis {
my ($data, $options) = @_;
my $current = $options->{blueprint} || $CONFIG_TREE;
my $mic = {};
my $bytes = '';
local $options->{depth} = ($options->{depth} || 0) + 1;
local $DEPTH = $options->{depth} if DEBUG;
if ($options->{depth} == 1 and defined $options->{mta_algorithm}) {
delete $data->{MtaConfigDelimiter};
$bytes .= encode_docsis({MtaConfigDelimiter => 1}, {depth => 1});
}
for my $name (sort { $current->{$a}{code} <=> $current->{$b}{code} } keys %$current) {
next unless defined $data->{$name};
my $syminfo = $current->{$name};
my ($type, $length, $value);
for my $item (_to_list($data->{$name}, $syminfo)) {
if ($syminfo->{nested}) {
warn "[DOCSIS]@{[' 'x$DEPTH]}Encode $name with encode_docsis\n" if DEBUG;
local @$options{qw(blueprint)} = ($current->{$name}{nested});
$value = encode_docsis($item, $options);
}
elsif (my $f = DOCSIS::ConfigFile::Encode->can($syminfo->{func})) {
warn "[DOCSIS]@{[' 'x$DEPTH]}Encode $name with $syminfo->{func}\n" if DEBUG;
if ($syminfo->{func} =~ /_list$/) {
$value = pack 'C*', $f->({value => _validate($item, $syminfo)});
}
elsif ($name eq 'SnmpMibObject') {
my @k = qw(type value);
local $item->{oid} = $item->{oid};
$value = pack 'C*',
$f->({value => {oid => delete $item->{oid}, map { shift(@k), $_ } %$item}});
}
else {
local $syminfo->{name} = $name;
$value = pack 'C*', $f->({value => _validate($item, $syminfo)});
}
}
else {
confess
qq(Can't locate object method "$syminfo->{func}" via package "DOCSIS::ConfigFile::Encode");
}
{
use warnings FATAL => 'all';
$type = pack 'C', $syminfo->{code};
$length = $syminfo->{lsize} == 2 ? pack('n', length $value) : pack('C', length $value);
}
$mic->{$name} = "$type$length$value";
$bytes .= $mic->{$name};
}
}
return $bytes if $options->{depth} != 1;
return _mta_eof($bytes, $options) if defined $options->{mta_algorithm};
return _cm_eof($bytes, $mic, $options);
}
sub _cm_eof {
my $mic = $_[1];
my $options = $_[2];
my $cmts_mic = '';
my $pads = 4 - (1 + length $_[0]) % 4;
my $eod_pad;
$mic->{CmMic} = pack('C*', 6, 16) . Digest::MD5::md5($_[0]);
$cmts_mic .= $mic->{$_} || '' for @CMTS_MIC;
$cmts_mic
= pack('C*', 7, 16) . Digest::HMAC_MD5::hmac_md5($cmts_mic, $options->{shared_secret} || '');
$eod_pad = pack('C', 255) . ("\0" x $pads);
return $_[0] . $mic->{CmMic} . $cmts_mic . $eod_pad;
}
sub _mta_eof {
my $mta_algorithm = $_[1]->{mta_algorithm} || '';
my $hash = '';
if ($mta_algorithm) {
croak "mta_algorithm must be empty string, md5 or sha1."
unless $mta_algorithm =~ /^(md5|sha1)$/;
$hash = $mta_algorithm eq 'md5' ? Digest::MD5::md5_hex($_[0]) : Digest::SHA::sha1_hex($_[0]);
$hash
= encode_docsis(
{SnmpMibObject => {oid => '1.3.6.1.4.1.4491.2.2.1.1.2.7.0', STRING => "0x$hash"}},
{depth => 1});
}
return $hash . $_[0] . encode_docsis({MtaConfigDelimiter => 255}, {depth => 1});
}
sub _to_list {
return $_[0] if $_[1]->{func} =~ /_list$/;
return @{$_[0]} if ref $_[0] eq 'ARRAY';
return $_[0];
}
# _validate($value, $syminfo);
sub _validate {
if ($_[1]->{limit}[1]) {
if ($_[0] =~ /^-?\d+$/) {
croak "[DOCSIS] $_[1]->{name} holds a too high value. ($_[0])" if $_[1]->{limit}[1] < $_[0];
croak "[DOCSIS] $_[1]->{name} holds a too low value. ($_[0])" if $_[0] < $_[1]->{limit}[0];
}
else {
my $length = ref $_[0] eq 'ARRAY' ? @{$_[0]} : length $_[0];
croak "[DOCSIS] $_[1]->{name} is too long. ($_[0])" if $_[1]->{limit}[1] < $length;
croak "[DOCSIS] $_[1]->{name} is too short. ($_[0])" if $length < $_[1]->{limit}[0];
}
}
return $_[0];
}
@CMTS_MIC = qw(
DownstreamFrequency UpstreamChannelId NetworkAccess
ClassOfService BaselinePrivacy VendorSpecific
CmMic MaxCPE TftpTimestamp
TftpModemAddress UsPacketClass DsPacketClass
UsServiceFlow DsServiceFlow MaxClassifiers
GlobalPrivacyEnable PHS SubMgmtControl
SubMgmtCpeTable SubMgmtFilters TestMode
);
$CONFIG_TREE = {
BaselinePrivacy => {
code => 17,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
AuthGraceTime => {code => 3, func => 'uint', lsize => 1, limit => [1, 6047999]},
AuthRejectTimeout => {code => 7, func => 'uint', lsize => 1, limit => [1, 600]},
AuthTimeout => {code => 1, func => 'uint', lsize => 1, limit => [1, 30]},
OperTimeout => {code => 4, func => 'uint', lsize => 1, limit => [1, 10]},
ReAuthTimeout => {code => 2, func => 'uint', lsize => 1, limit => [1, 30]},
ReKeyTimeout => {code => 5, func => 'uint', lsize => 1, limit => [1, 10]},
SAMapMaxRetries => {code => 9, func => 'uint', lsize => 1, limit => [0, 10]},
SAMapWaitTimeout => {code => 8, func => 'uint', lsize => 1, limit => [1, 10]},
TEKGraceTime => {code => 6, func => 'uint', lsize => 1, limit => [1, 302399]},
},
},
ClassOfService => {
code => 4,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
ClassID => {code => 1, func => 'uchar', lsize => 1, limit => [1, 16]},
GuaranteedUp => {code => 5, func => 'uint', lsize => 1, limit => [0, 10000000]},
MaxBurstUp => {code => 6, func => 'ushort', lsize => 1, limit => [0, 65535]},
MaxRateDown => {code => 2, func => 'uint', lsize => 1, limit => [0, 52000000]},
MaxRateUp => {code => 3, func => 'uint', lsize => 1, limit => [0, 10000000]},
PriorityUp => {code => 4, func => 'uchar', lsize => 1, limit => [0, 7]},
PrivacyEnable => {code => 7, func => 'uchar', lsize => 1, limit => [0, 1]},
},
},
CmMic => {code => 6, func => 'mic', lsize => 1, limit => [0, 0]},
CmtsMic => {code => 7, func => 'mic', lsize => 1, limit => [0, 0]},
CpeMacAddress => {code => 14, func => 'ether', lsize => 1, limit => [0, 0]},
DocsisTwoEnable => {code => 39, func => 'uchar', lsize => 1, limit => [0, 1]},
DownstreamFrequency => {code => 1, func => 'uint', lsize => 1, limit => [88000000, 860000000]},
DsChannelList => {
code => 41,
func => 'nested',
lsize => 1,
limit => [1, 255],
nested => {
DefaultScanTimeout => {code => 3, func => 'ushort', lsize => 1, limit => [0, 65535]},
DsFreqRange => {
code => 2,
func => 'nested',
lsize => 1,
limit => [1, 255],
nested => {
DsFreqRangeEnd => {code => 3, func => 'uint', lsize => 1, limit => [0, 4294967295]},
DsFreqRangeStart => {code => 2, func => 'uint', lsize => 1, limit => [0, 4294967295]},
DsFreqRangeStepSize => {code => 4, func => 'uint', lsize => 1, limit => [0, 4294967295]},
DsFreqRangeTimeout => {code => 1, func => 'ushort', lsize => 1, limit => [0, 65535]},
},
},
SingleDsChannel => {
code => 1,
func => 'nested',
lsize => 1,
limit => [1, 255],
nested => {
SingleDsFrequency => {code => 2, func => 'uint', lsize => 1, limit => [0, 4294967295]},
SingleDsTimeout => {code => 1, func => 'ushort', lsize => 1, limit => [0, 65535]},
},
},
},
},
DsPacketClass => {
code => 23,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
ActivationState => {code => 6, func => 'uchar', lsize => 1, limit => [0, 1]},
ClassifierId => {code => 2, func => 'ushort', lsize => 1, limit => [1, 65535]},
ClassifierRef => {code => 1, func => 'uchar', lsize => 1, limit => [1, 255]},
DscAction => {code => 7, func => 'uchar', lsize => 1, limit => [0, 2]},
IEEE802Classifier => {
code => 11,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
UserPriority => {code => 1, func => 'ushort', lsize => 1, limit => [0, 0]},
VlanID => {code => 2, func => 'ushort', lsize => 1, limit => [0, 0]},
},
},
IpPacketClassifier => {
code => 9,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
DstPortEnd => {code => 10, func => 'ushort', lsize => 1, limit => [0, 65535]},
DstPortStart => {code => 9, func => 'ushort', lsize => 1, limit => [0, 65535]},
IpDstAddr => {code => 5, func => 'ip', lsize => 1, limit => [0, 0]},
IpDstMask => {code => 6, func => 'ip', lsize => 1, limit => [0, 0]},
IpProto => {code => 2, func => 'ushort', lsize => 1, limit => [0, 257]},
IpSrcAddr => {code => 3, func => 'ip', lsize => 1, limit => [0, 0]},
IpSrcMask => {code => 4, func => 'ip', lsize => 1, limit => [0, 0]},
IpTos => {code => 1, func => 'hexstr', lsize => 1, limit => [0, 0]},
SrcPortEnd => {code => 8, func => 'ushort', lsize => 1, limit => [0, 65535]},
SrcPortStart => {code => 7, func => 'ushort', lsize => 1, limit => [0, 65535]},
},
},
LLCPacketClassifier => {
code => 10,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
DstMacAddress => {code => 1, func => 'ether', lsize => 1, limit => [0, 0]},
EtherType => {code => 3, func => 'hexstr', lsize => 1, limit => [0, 0]},
SrcMacAddress => {code => 2, func => 'ether', lsize => 1, limit => [0, 0]},
},
},
RulePriority => {code => 5, func => 'uchar', lsize => 1, limit => [0, 255]},
ServiceFlowId => {code => 4, func => 'uint', lsize => 1, limit => [1, 4294967295]},
ServiceFlowRef => {code => 3, func => 'ushort', lsize => 1, limit => [1, 65535]},
},
},
DsServiceFlow => {
code => 25,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
ActQosParamsTimeout => {code => 12, func => 'ushort', lsize => 1, limit => [0, 65535]},
AdmQosParamsTimeout => {code => 13, func => 'ushort', lsize => 1, limit => [0, 65535]},
DsServiceFlowId => {code => 2, func => 'uint', lsize => 1, limit => [1, 4294967295]},
DsServiceFlowRef => {code => 1, func => 'ushort', lsize => 1, limit => [1, 65535]},
DsVendorSpecific => {code => 43, func => 'vendor', lsize => 1, limit => [0, 0]},
MaxDsLatency => {code => 14, func => 'uint', lsize => 1, limit => [0, 0]},
MaxRateSustained => {code => 8, func => 'uint', lsize => 1, limit => [0, 4294967295]},
MaxTrafficBurst => {code => 9, func => 'uint', lsize => 1, limit => [0, 4294967295]},
MinReservedRate => {code => 10, func => 'uint', lsize => 1, limit => [0, 4294967295]},
MinResPacketSize => {code => 11, func => 'ushort', lsize => 1, limit => [0, 65535]},
QosParamSetType => {code => 6, func => 'uchar', lsize => 1, limit => [0, 255]},
ServiceClassName => {code => 4, func => 'stringz', lsize => 1, limit => [2, 16]},
TrafficPriority => {code => 7, func => 'uchar', lsize => 1, limit => [0, 7]},
},
},
GenericTLV => {code => 255, func => 'no_value', lsize => 0, limit => [0, 0]},
GlobalPrivacyEnable => {code => 29, func => 'uchar', lsize => 1, limit => [0, 0]},
MaxClassifiers => {code => 28, func => 'ushort', lsize => 1, limit => [0, 0]},
MaxCPE => {code => 18, func => 'uchar', lsize => 1, limit => [1, 254]},
MfgCVCData => {code => 32, func => 'hexstr', lsize => 1, limit => [0, 0]},
ModemCapabilities => {
code => 5,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
BaselinePrivacySupport => {code => 6, func => 'uchar', lsize => 1, limit => [0, 1]},
ConcatenationSupport => {code => 1, func => 'uchar', lsize => 1, limit => [0, 1]},
DCCSupport => {code => 12, func => 'uchar', lsize => 1, limit => [0, 1]},
DownstreamSAIDSupport => {code => 7, func => 'uchar', lsize => 1, limit => [0, 255]},
FragmentationSupport => {code => 3, func => 'uchar', lsize => 1, limit => [0, 1]},
IGMPSupport => {code => 5, func => 'uchar', lsize => 1, limit => [0, 1]},
ModemDocsisVersion => {code => 2, func => 'uchar', lsize => 1, limit => [0, 2]},
PHSSupport => {code => 4, func => 'uchar', lsize => 1, limit => [0, 1]},
UpstreamSIDSupport => {code => 8, func => 'uchar', lsize => 1, limit => [0, 255]},
},
},
MtaConfigDelimiter => {code => 254, func => 'uchar', lsize => 1, limit => [1, 255]},
NetworkAccess => {code => 3, func => 'uchar', lsize => 1, limit => [0, 1]},
PHS => {
code => 26,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
PHSClassifierId => {code => 2, func => 'ushort', lsize => 1, limit => [1, 65535]},
PHSClassifierRef => {code => 1, func => 'uchar', lsize => 1, limit => [1, 255]},
PHSField => {code => 7, func => 'hexstr', lsize => 1, limit => [1, 255]},
PHSIndex => {code => 8, func => 'uchar', lsize => 1, limit => [1, 255]},
PHSMask => {code => 9, func => 'hexstr', lsize => 1, limit => [1, 255]},
PHSServiceFlowId => {code => 4, func => 'uint', lsize => 1, limit => [1, 4294967295]},
PHSServiceFlowRef => {code => 3, func => 'ushort', lsize => 1, limit => [1, 65535]},
PHSSize => {code => 10, func => 'uchar', lsize => 1, limit => [1, 255]},
PHSVerify => {code => 11, func => 'uchar', lsize => 1, limit => [0, 1]},
},
},
SnmpCpeAccessControl => {code => 55, func => 'uchar', lsize => 1, limit => [0, 1]},
SnmpMibObject => {code => 11, func => 'snmp_object', lsize => 1, limit => [1, 255]},
SnmpV3Kickstart => {
code => 34,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
SnmpV3MgrPublicNumber => {code => 2, func => 'hexstr', lsize => 1, limit => [1, 514]},
SnmpV3SecurityName => {code => 1, func => 'string', lsize => 1, limit => [1, 16]},
},
},
SnmpV3TrapReceiver => {
code => 38,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
SnmpV3TrapRxFilterOID => {code => 6, func => 'ushort', lsize => 1, limit => [1, 5]},
SnmpV3TrapRxIP => {code => 1, func => 'ip', lsize => 1, limit => [0, 0]},
SnmpV3TrapRxPort => {code => 2, func => 'ushort', lsize => 1, limit => [0, 0]},
SnmpV3TrapRxRetries => {code => 5, func => 'ushort', lsize => 1, limit => [0, 65535]},
SnmpV3TrapRxSecurityName => {code => 7, func => 'string', lsize => 1, limit => [1, 16]},
SnmpV3TrapRxTimeout => {code => 4, func => 'ushort', lsize => 1, limit => [0, 65535]},
SnmpV3TrapRxType => {code => 3, func => 'ushort', lsize => 1, limit => [1, 5]},
},
},
SubMgmtControl => {code => 35, func => 'hexstr', lsize => 1, limit => [3, 3]},
SubMgmtCpeTable => {code => 36, func => 'hexstr', lsize => 1, limit => [0, 0]},
SubMgmtFilters => {code => 37, func => 'ushort_list', lsize => 1, limit => [0, 20]},
SwUpgradeFilename => {code => 9, func => 'string', lsize => 1, limit => [0, 0]},
SwUpgradeServer => {code => 21, func => 'ip', lsize => 1, limit => [0, 0]},
TestMode => {code => 40, func => 'hexstr', lsize => 1, limit => [0, 1]},
TftpModemAddress => {code => 20, func => 'ip', lsize => 1, limit => [0, 0]},
TftpTimestamp => {code => 19, func => 'uint', lsize => 1, limit => [0, 4294967295]},
UpstreamChannelId => {code => 2, func => 'uchar', lsize => 1, limit => [0, 255]},
UsPacketClass => {
code => 22,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
ActivationState => {code => 6, func => 'uchar', lsize => 1, limit => [0, 1]},
ClassifierId => {code => 2, func => 'ushort', lsize => 1, limit => [1, 65535]},
ClassifierRef => {code => 1, func => 'uchar', lsize => 1, limit => [1, 255]},
DscAction => {code => 7, func => 'uchar', lsize => 1, limit => [0, 2]},
IEEE802Classifier => {
code => 11,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
UserPriority => {code => 1, func => 'ushort', lsize => 1, limit => [0, 0]},
VlanID => {code => 2, func => 'ushort', lsize => 1, limit => [0, 0]},
},
},
IpPacketClassifier => {
code => 9,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
DstPortEnd => {code => 10, func => 'ushort', lsize => 1, limit => [0, 65535]},
DstPortStart => {code => 9, func => 'ushort', lsize => 1, limit => [0, 65535]},
IpDstAddr => {code => 5, func => 'ip', lsize => 1, limit => [0, 0]},
IpDstMask => {code => 6, func => 'ip', lsize => 1, limit => [0, 0]},
IpProto => {code => 2, func => 'ushort', lsize => 1, limit => [0, 257]},
IpSrcAddr => {code => 3, func => 'ip', lsize => 1, limit => [0, 0]},
IpSrcMask => {code => 4, func => 'ip', lsize => 1, limit => [0, 0]},
IpTos => {code => 1, func => 'hexstr', lsize => 1, limit => [0, 0]},
SrcPortEnd => {code => 8, func => 'ushort', lsize => 1, limit => [0, 65535]},
SrcPortStart => {code => 7, func => 'ushort', lsize => 1, limit => [0, 65535]},
}
},
LLCPacketClassifier => {
code => 10,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
DstMacAddress => {code => 1, func => 'ether', lsize => 1, limit => [0, 0]},
EtherType => {code => 3, func => 'hexstr', lsize => 1, limit => [0, 0]},
SrcMacAddress => {code => 2, func => 'ether', lsize => 1, limit => [0, 0]},
},
},
RulePriority => {code => 5, func => 'uchar', lsize => 1, limit => [0, 255]},
ServiceFlowId => {code => 4, func => 'uint', lsize => 1, limit => [1, 4294967295]},
ServiceFlowRef => {code => 3, func => 'ushort', lsize => 1, limit => [1, 65535]},
},
},
UsServiceFlow => {
code => 24,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
ActQosParamsTimeout => {code => 12, func => 'ushort', lsize => 1, limit => [0, 65535]},
AdmQosParamsTimeout => {code => 13, func => 'ushort', lsize => 1, limit => [0, 65535]},
GrantsPerInterval => {code => 22, func => 'uchar', lsize => 1, limit => [0, 127]},
IpTosOverwrite => {code => 23, func => 'hexstr', lsize => 1, limit => [0, 255]},
MaxConcatenatedBurst => {code => 14, func => 'ushort', lsize => 1, limit => [0, 65535]},
MaxRateSustained => {code => 8, func => 'uint', lsize => 1, limit => [0, 0]},
MaxTrafficBurst => {code => 9, func => 'uint', lsize => 1, limit => [0, 0]},
MinReservedRate => {code => 10, func => 'uint', lsize => 1, limit => [0, 0]},
MinResPacketSize => {code => 11, func => 'ushort', lsize => 1, limit => [0, 65535]},
NominalGrantInterval => {code => 20, func => 'uint', lsize => 1, limit => [0, 0]},
NominalPollInterval => {code => 17, func => 'uint', lsize => 1, limit => [0, 0]},
QosParamSetType => {code => 6, func => 'uchar', lsize => 1, limit => [0, 255]},
RequestOrTxPolicy => {code => 16, func => 'hexstr', lsize => 1, limit => [0, 255]},
SchedulingType => {code => 15, func => 'uchar', lsize => 1, limit => [0, 6]},
ServiceClassName => {code => 4, func => 'stringz', lsize => 1, limit => [2, 16]},
ToleratedGrantJitter => {code => 21, func => 'uint', lsize => 1, limit => [0, 0]},
ToleratedPollJitter => {code => 18, func => 'uint', lsize => 1, limit => [0, 0]},
TrafficPriority => {code => 7, func => 'uchar', lsize => 1, limit => [0, 7]},
UnsolicitedGrantSize => {code => 19, func => 'ushort', lsize => 1, limit => [0, 65535]},
UsServiceFlowId => {code => 2, func => 'uint', lsize => 1, limit => [1, 4294967295]},
UsServiceFlowRef => {code => 1, func => 'ushort', lsize => 1, limit => [1, 65535]},
UsVendorSpecific => {code => 43, func => 'vendor', lsize => 1, limit => [0, 0]},
},
},
eRouter => {
code => 202,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
InitializationMode => {code => 1, func => 'uchar', lsize => 1, limit => [0, 3]},
ManagementServer => {
code => 2,
func => 'nested',
lsize => 1,
limit => [0, 0],
nested => {
EnableCWMP => {code => 1, func => 'uchar', lsize => 1, limit => [0, 1]},
URL => {code => 2, func => 'string', lsize => 1, limit => [0, 0]},
Username => {code => 3, func => 'string', lsize => 1, limit => [0, 0]},
Password => {code => 4, func => 'string', lsize => 1, limit => [0, 0]},
ConnectionRequestUsername => {code => 5, func => 'string', lsize => 1, limit => [0, 0]},
ConnectionRequestPassword => {code => 6, func => 'string', lsize => 1, limit => [0, 0]},
ACSOverride => {code => 7, func => 'uchar', lsize => 1, limit => [0, 1]},
},
},
InitializationModeOverride => {code => 3, func => 'uchar', lsize => 1, limit => [0, 1]},
},
},
VendorSpecific => {code => 43, func => 'vendor', lsize => 1, limit => [0, 0]},
};
=encoding utf8
=head1 NAME
DOCSIS::ConfigFile - Decodes and encodes DOCSIS config files
=head1 DESCRIPTION
L<DOCSIS::ConfigFile> is a class which provides functionality to decode and
encode L<DOCSIS|http://www.cablelabs.com> (Data over Cable Service Interface
Specifications) config files.
This module is used as a layer between any human readable data and
the binary structure.
The files are usually served using a L<TFTP server|Mojo::TFTPd>, after a
L<cable modem|http://en.wikipedia.org/wiki/Cable_modem> or MTA (Multimedia
Terminal Adapter) has recevied an IP address from a L<DHCP|Net::ISC::DHCPd>
server. These files are L<binary encode|DOCSIS::ConfigFile::Encode> using a
variety of functions, but all the data in the file are constructed by TLVs
(type-length-value) blocks. These can be nested and concatenated.
See the source code or L<https://thorsen.pm/docsisious> for list of
supported parameters.
=head1 SYNOPSIS
use DOCSIS::ConfigFile qw(encode_docsis decode_docsis);
$data = decode_docsis $bytes;
$bytes = encode_docsis({
GlobalPrivacyEnable => 1,
MaxCPE => 2,
NetworkAccess => 1,
BaselinePrivacy => {
AuthTimeout => 10,
ReAuthTimeout => 10,
AuthGraceTime => 600,
OperTimeout => 1,
ReKeyTimeout => 1,
TEKGraceTime => 600,
AuthRejectTimeout => 60,
SAMapWaitTimeout => 1,
SAMapMaxRetries => 4
},
SnmpMibObject => [
{oid => "1.3.6.1.4.1.1.77.1.6.1.1.6.2", INTEGER => 1},
{oid => "1.3.6.1.4.1.1429.77.1.6.1.1.6.2", STRING => "bootfile.bin"}
],
VendorSpecific => {id => "0x0011ee", options => [30 => "0xff", 31 => "0x00", 32 => "0x28"]}
});
=head1 OPTIONAL MODULE
You can install the L<SNMP.pm|SNMP> module to translate between SNMP
OID formats. With the module installed, you can define the C<SnmpMibObject>
like the example below, instead of using numeric OIDs:
encode_docsis({
SnmpMibObject => [
{oid => "docsDevNmAccessIp.1", IPADDRESS => "10.0.0.1"},
{oid => "docsDevNmAccessIpMask.1", IPADDRESS => "255.255.255.255"},
]
});
=head1 WEB APPLICATION
There is an example web application bundled with this distribution called
"Docsisious". To run this application, you need to install L<Mojolicious> and
L<YAML::XS>:
$ curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org DOCSIS::ConfigFile Mojolicious;
After installing the modules above, you can run the web app like this:
$ docsisious --listen http://*:8000;
And then open your favorite browser at L<http://localhost:8000>. To see a live
demo, you can visit L<https://thorsen.pm/docsisious>.
=head1 FUNCTIONS
=head2 decode_docsis
$data = decode_docsis($byte_string);
$data = decode_docsis(\$path_to_file);
Used to decode a DOCSIS config file into a data structure. The output
C<$data> can be used as input to L</encode_docsis>. Note: C<$data>
will only contain array-refs if the DOCSIS parameter occur more than
once.
=head2 encode_docsis
$byte_string = encode_docsis(\%data, \%args);
Used to encode a data structure into a DOCSIS config file. Each of the keys
in C<$data> can either hold a hash- or array-ref. An array-ref is used if
the same DOCSIS parameter occur multiple times. These two formats will result
in the same C<$byte_string>:
# Only one SnmpMibObject
encode_docsis({
SnmpMibObject => {
oid => "1.3.6.1.4.1.1429.77.1.6.1.1.6.2", STRING => "bootfile.bin"
}
})
# Allow one or more SnmpMibObjects
encode_docsis({
SnmpMibObject => [
{oid => "1.3.6.1.4.1.1429.77.1.6.1.1.6.2", STRING => "bootfile.bin"}
]
})
Possible C<%args>:
=over 4
=item * mta_algorithm
This argument is required when encoding MTA config files. Can be set to
either empty string, "sha1" or "md5".
=item * shared_secret
This argument is optional, but will be used as the shared secret used to
increase security between the cable modem and CMTS.
=back
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2014-2018, Jan Henning Thorsen
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=head1 CREDITS
=head2 Font Awesome
C<docsisious> bundles L<Font Awesome|https://fontawesome.com/>.
=head1 AUTHOR
Jan Henning Thorsen - C<[email protected]>
=cut