File Coverage

blib/lib/Data/Radius/Decode.pm
Criterion Covered Total %
statement 67 75 89.3
branch 14 22 63.6
condition 2 5 40.0
subroutine 22 23 95.6
pod 0 15 0.0
total 105 140 75.0


line stmt bran cond sub pod time code
1             package Data::Radius::Decode;
2              
3 4     4   61393 use v5.10;
  4         15  
4 4     4   41 use strict;
  4         7  
  4         89  
5 4     4   25 use warnings;
  4         9  
  4         152  
6 4     4   547 use bytes;
  4         16  
  4         20  
7 4     4   595 use Socket qw(inet_ntop inet_pton AF_INET AF_INET6);
  4         3332  
  4         326  
8              
9             use constant {
10 4         297 ATTR_CISCO_AVPAIR => 1,
11             VENDOR_CISCO => 'Cisco',
12 4     4   25 };
  4         6  
13              
14 4     4   20 use Exporter qw(import);
  4         7  
  4         208  
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 4     4   389 use Data::Radius::Util qw(is_enum_type);
  4         8  
  4         3193  
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 10     10 0 23 sub decode_string { $_[0] }
61              
62             sub decode_string_tag {
63 1     1 0 2 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 1 50       3 return if (length($value) < 1);
68              
69 1         3 my $tag = unpack('C', substr($value, 0, 1));
70 1 50       3 if ($tag > 0x1F) {
71 0         0 return ($value, undef);
72             }
73 1         13 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 2     2 0 3 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 2         5 my $tag = unpack('C', substr($value, 0, 1, "\x00"));
88 2         4 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 7 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         4 return $decode_map{ipaddr}->($ip);
111             }
112 1         3 return $decode_map{ipv6addr}->($ip);
113             }
114              
115             sub decode_avpair {
116 3     3 0 8 my ($value, $attr, $dict) = @_;
117 3 50 50     21 if ( ($attr->{vendor} // '') eq VENDOR_CISCO) {
118             # Cisco hack
119 3 100       15 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         50 $value =~ s/^\Q$attr->{name}\E\s*=//;
127             }
128             }
129              
130 3         10 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         6 while ($pos < $len) {
141 3         13 my ($attr_id, $attr_len) = unpack('C C', substr($value, $pos, 2));
142 3         7 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       9 if (! $attr) {
146 0         0 push @list, {Name => $attr_id, Value => $attr_val, Unknown => 1};
147             }
148             else {
149 3         7 my $decoded = decode($attr, $attr_val, $dict);
150 3 100       10 if (is_enum_type($attr->{type})) {
151 1   33     5 $decoded = $dict->constant($attr->{name}, $decoded) // $decoded;
152             }
153              
154 3         41 push @list, {Name => $attr->{name}, Value => $decoded, Type => $attr->{type}};
155             }
156              
157 3         7 $pos += $attr_len;
158             }
159              
160 2         6 return \@list;
161             }
162              
163             sub decode {
164 28     28 0 13046 my ($attr, $value, $dict) = @_;
165              
166 28 100       127 my $decoder = $attr->{type} . ($attr->{has_tag} ? '_tag' : '');
167 28         85 my ($decoded, $tag) = $decode_map{ $decoder }->($value, $attr, $dict);
168 28 100       127 return wantarray ? ($decoded, $tag) : $decoded;
169             }
170              
171             1;