File Coverage

blib/lib/Data/Radius/Decode.pm
Criterion Covered Total %
statement 59 75 78.6
branch 11 22 50.0
condition 2 5 40.0
subroutine 20 23 86.9
pod 0 15 0.0
total 92 140 65.7


line stmt bran cond sub pod time code
1             package Data::Radius::Decode;
2              
3 2     2   70598 use v5.10;
  2         8  
4 2     2   12 use strict;
  2         3  
  2         39  
5 2     2   20 use warnings;
  2         4  
  2         82  
6 2     2   625 use bytes;
  2         16  
  2         9  
7 2     2   653 use Socket qw(inet_ntop inet_pton AF_INET AF_INET6);
  2         3934  
  2         254  
8              
9             use constant {
10 2         202 ATTR_CISCO_AVPAIR => 1,
11             VENDOR_CISCO => 'Cisco',
12 2     2   16 };
  2         3  
13              
14 2     2   14 use Exporter qw(import);
  2         4  
  2         115  
15             our @EXPORT_OK = qw(
16             decode
17              
18             decode_string
19             decode_int
20             decode_byte
21             decode_short
22             decode_signed
23             decode_ipaddr
24             decode_ipv6addr
25             decode_combo_ip
26             decode_octets
27             decode_avpair
28             decode_tlv
29             );
30              
31 2     2   442 use Data::Radius::Util qw(is_enum_type);
  2         16  
  2         1875  
32              
33             # type decoders
34             # $coderef->($value, $attr, $dictionary)
35             my %decode_map = (
36             string => \&decode_string,
37             string_tag => \&decode_string_tag,
38             integer => \&decode_int,
39             integer_tag => \&decode_int_tag,
40             byte => \&decode_byte,
41             short => \&decode_short,
42             signed => \&decode_signed,
43             ipaddr => \&decode_ipaddr,
44             ipv6addr => \&decode_ipv6addr,
45             avpair => \&decode_avpair,
46             'combo-ip' => \&decode_combo_ip,
47             octets => \&decode_octets,
48             tlv => \&decode_tlv,
49             # Unix timestamp
50             date => \&decode_int,
51             #TODO Ascend binary encoding
52             # abinary => ...
53             );
54              
55             if (!defined inet_pton(AF_INET6, '::1')) {
56             require Net::IP;
57             $decode_map{ipv6addr} = \&decode_ipv6addr_pp;
58             }
59              
60 6     6 0 14 sub decode_string { $_[0] }
61              
62             sub decode_string_tag {
63 0     0 0 0 my $value = shift;
64             # https://tools.ietf.org/html/rfc2868#section-3.3
65             # If the Tag field is greater than 0x1F, it SHOULD be
66             # interpreted as the first byte of the following String field
67 0 0       0 return if (length($value) < 1);
68              
69 0         0 my $tag = unpack('C', substr($value, 0, 1));
70 0 0       0 if ($tag > 0x1F) {
71 0         0 return ($value, undef);
72             }
73 0         0 return (substr($value, 1), $tag);
74             }
75              
76 1     1 0 7 sub decode_int { unpack('N', $_[0]) }
77 2     2 0 7 sub decode_byte { unpack('C', $_[0]) }
78 1     1 0 5 sub decode_short { unpack('S>', $_[0]) }
79 1     1 0 6 sub decode_signed { unpack('l>', $_[0]) }
80              
81             sub decode_int_tag {
82 0     0 0 0 my $value = shift;
83             # https://tools.ietf.org/html/rfc6158#section-3.2.2
84             # when integer values are tagged, the value portion is reduced to three bytes
85              
86             # replace tag by 0 to make unpack() value work
87 0         0 my $tag = unpack('C', substr($value, 0, 1, "\x00"));
88 0         0 return (unpack('N', $value), $tag);
89             }
90              
91 2     2 0 16 sub decode_ipaddr { inet_ntop(AF_INET, $_[0]) }
92 2     2 0 13 sub decode_ipv6addr { inet_ntop(AF_INET6, $_[0]) }
93              
94             sub decode_ipv6addr_pp {
95 0     0 0 0 my $value = shift;
96              
97 0         0 my $binary = unpack( 'B*', $value );
98 0 0       0 return undef if (! $binary);
99 0         0 my $ip_val = Net::IP::ip_bintoip( $binary, 6 );
100 0 0       0 return undef if (! $ip_val);
101 0         0 return Net::IP::ip_compress_address( $ip_val, 6 );
102             }
103              
104 1     1 0 6 sub decode_octets { '0x'.unpack("H*", $_[0]) }
105              
106             sub decode_combo_ip {
107 2     2 0 4 my $ip = shift;
108              
109 2 100       7 if (length($ip) == 4) {
110 1         3 return $decode_map{ipaddr}->($ip);
111             }
112 1         4 return $decode_map{ipv6addr}->($ip);
113             }
114              
115             sub decode_avpair {
116 3     3 0 6 my ($value, $attr, $dict) = @_;
117 3 50 50     17 if ( ($attr->{vendor} // '') eq VENDOR_CISCO) {
118             # Cisco hack
119 3 100       11 if ( $attr->{id} == ATTR_CISCO_AVPAIR ) {
120             # Cisco-AVPair = "h323-foo-bar=baz"
121             # leave it as-is
122             }
123             else {
124             # h323-foo-bar = "h323-foo-bar = baz"
125             # cut attribute name
126 2         39 $value =~ s/^\Q$attr->{name}\E\s*=//;
127             }
128             }
129              
130 3         11 return $value;
131             }
132              
133             sub decode_tlv {
134 2     2 0 4 my ($value, $parent, $dict) = @_;
135              
136 2         4 my $pos = 0;
137 2         4 my $len = length($value);
138              
139 2         5 my @list = ();
140 2         7 while ($pos < $len) {
141 3         14 my ($attr_id, $attr_len) = unpack('C C', substr($value, $pos, 2));
142 3         8 my $attr_val = substr($value, $pos + 2, $attr_len - 2);
143              
144 3         10 my $attr = $dict->tlv_attribute_name($parent, $attr_id);
145 3 50       8 if (! $attr) {
146 0         0 push @list, {Name => $attr_id, Value => $attr_val, Unknown => 1};
147             }
148             else {
149 3         9 my $decoded = decode($attr, $attr_val, $dict);
150 3 100       13 if (is_enum_type($attr->{type})) {
151 1   33     11 $decoded = $dict->constant($attr->{name}, $decoded) // $decoded;
152             }
153              
154 3         51 push @list, {Name => $attr->{name}, Value => $decoded, Type => $attr->{type}};
155             }
156              
157 3         10 $pos += $attr_len;
158             }
159              
160 2         6 return \@list;
161             }
162              
163             sub decode {
164 21     21 0 3590 my ($attr, $value, $dict) = @_;
165              
166 21 50       71 my $decoder = $attr->{type} . ($attr->{has_tag} ? '_tag' : '');
167 21         62 my ($decoded, $tag) = $decode_map{ $decoder }->($value, $attr, $dict);
168 21 100       103 return wantarray ? ($decoded, $tag) : $decoded;
169             }
170              
171             1;