File Coverage

blib/lib/Data/Radius/DictionaryParser.pm
Criterion Covered Total %
statement 110 124 88.7
branch 39 50 78.0
condition 8 13 61.5
subroutine 14 14 100.0
pod 0 4 0.0
total 171 205 83.4


line stmt bran cond sub pod time code
1             package Data::Radius::DictionaryParser;
2              
3 4     4   51 use v5.10;
  4         14  
4 4     4   37 use strict;
  4         9  
  4         115  
5 4     4   42 use warnings;
  4         9  
  4         134  
6              
7 4     4   1517 use IO::File ();
  4         18994  
  4         93  
8 4     4   29 use File::Spec ();
  4         11  
  4         66  
9 4     4   20 use File::Basename ();
  4         15  
  4         88  
10              
11 4     4   22 use Digest::MD5 qw(md5_hex);
  4         6  
  4         4813  
12              
13             # parser state
14             my $begin_vendor = undef;
15             my $begin_tlv = undef;
16             # map id to name, {vendor => {id => name}}
17             my %dict_id = ();
18             # map name to id
19             my %dict_attr = ();
20             my %dict_const_name = ();
21             my %dict_const_value = ();
22             my %dict_vendor_name = ();
23             my %dict_vendor_id = ();
24              
25             my %included_files = ();
26              
27             sub new {
28 6     6 0 17 my $class = shift;
29 6         24 cleanup();
30 6         30 return bless {}, $class;
31             }
32              
33             sub _create_dict_from_current_state {
34 6     6   21 my ($self) = @_;
35              
36 6         5856 return Data::Radius::Dictionary->new(
37             attr_id => { %dict_id },
38             attr_name => { %dict_attr },
39             const_name => { %dict_const_name },
40             const_value => { %dict_const_value },
41             vnd_name => { %dict_vendor_name },
42             vnd_id => { %dict_vendor_id },
43             );
44             }
45              
46             sub parse_str_array {
47 1     1 0 4 my ($self, $str_array) = @_;
48              
49 1         2 my $synthetic_fname = md5_hex( @{$str_array} );
  1         9  
50 1 50       6 return if ( $included_files{$synthetic_fname} );
51 1         3 $included_files{$synthetic_fname} = 1;
52              
53             # since it's not file on disk, all $INCLUDEs are built relative to CWD
54 1   50     6 my $include_dir = $ENV{PWD} // '/';
55              
56 1         2 for my $line ( @{$str_array} ) {
  1         2  
57 2         6 $self->_parse_line($line, $include_dir);
58             }
59              
60 1         4 return $self->_create_dict_from_current_state;
61             }
62              
63             sub parse_file {
64 5     5 0 15 my ($self, $file) = @_;
65 5         20 $self->_load_file($file);
66 5         68 return $self->_create_dict_from_current_state;
67             }
68              
69             sub _load_file {
70 101     101   248 my ($self, $file) = @_;
71              
72 101 50       264 return undef if ( $included_files{$file} );
73              
74 101   33     539 my $fh = IO::File->new($file)
75             || warn sprintf('Failed to open file "%s": %s', $file, $!);
76              
77 101         8914 $included_files{$file} = 1;
78              
79             # INCLUDEs must be treated relatively to current file
80 101         3137 my $include_dir = File::Basename::dirname($file);
81              
82 101         2437 while ( my $line = $fh->getline ) {
83 13954         318692 $self->_parse_line($line, $include_dir);
84             }
85              
86 101         3672 $fh->close;
87              
88 101         1963 return 1;
89             }
90              
91             sub cleanup {
92 6     6 0 18 my ($self) = @_;
93              
94 6         15 $begin_vendor = undef;
95 6         10 $begin_tlv = undef;
96              
97 6         19 %dict_id = ();
98 6         145 %dict_attr = ();
99 6         29 %dict_const_name = ();
100 6         21 %dict_const_value = ();
101 6         17 %dict_vendor_name = ();
102 6         15 %dict_vendor_id = ();
103 6         24 %included_files = ();
104              
105 6         12 return undef;
106             }
107              
108             sub _parse_line {
109 13956     13956   27092 my ($self, $line, $include_dir) = @_;
110 13956   33     25084 $include_dir //= $ENV{PWD};
111              
112 13956         21316 my ($cmd, $name, $id, $type, $vendor, $has_tag, $has_options, $encrypt);
113              
114 13956         26268 $line =~ s/#.*$//;
115 13956 100       80345 return undef if ( $line =~ /^\s*$/ );
116 10952         17812 chomp $line;
117              
118 10952         49808 ($cmd, $name, $id, $type, $vendor) = split(/\s+/, $line);
119 10952         21629 $cmd = lc($cmd);
120 10952         15267 $has_options = 0;
121 10952         14076 $has_tag = 0;
122 10952         15617 $encrypt = undef;
123              
124 10952 100       23244 if ($cmd eq 'attribute') {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
125             # 'vendor' part can be an options - in FreeRADIUS dictionary format
126 5154 100       9317 if ($vendor) {
127             # there could be combination of both options:
128 1416 100       3010 if ($vendor =~ /has_tag/) {
129 156         230 $has_tag = 1;
130 156         273 $has_options = 1;
131             }
132 1416 100       2644 if ($vendor =~ /encrypt=(\d)/) {
133             #TODO encryption methods not supported now
134 80         192 $encrypt = $1;
135 80         118 $has_options = 1;
136             }
137              
138 1416 100       2503 if ($has_options) {
139 232         340 $vendor = undef;
140             }
141             }
142              
143 5154   100     16205 $vendor ||= $begin_vendor;
144              
145 5154 50       10766 if (exists $dict_attr{ $name }) {
146 0         0 warn "Duplicated attribute name $name";
147             }
148              
149 5154         21935 my $a_info = {
150             id => $id,
151             name => $name,
152             type => $type,
153             vendor => $vendor,
154             has_tag => $has_tag,
155             encrypt => $encrypt,
156             };
157              
158 5154         14017 $dict_attr{ $name } = $a_info;
159              
160 5154 100       9009 if ($begin_tlv) {
161 212         412 $a_info->{parent} = $begin_tlv;
162              
163 212         382 my $parent = $dict_attr{ $begin_tlv };
164 212         474 $parent->{tlv_attr_name}{ $name } = $a_info;
165 212         544 $parent->{tlv_attr_id}{ $id } = $a_info;
166             }
167             else {
168 4942   100     15553 $dict_id{ $vendor // '' }{ $id } = $a_info;
169             }
170             }
171             elsif ($cmd eq 'value') {
172             # VALUE NAS-Port-Type Ethernet 15
173 5512         9929 my ($v_name, $v_val) = ($id, $type);
174              
175 5512 50       11254 if (! exists $dict_attr{ $name }) {
176 0         0 warn "Value for unknown attribute $name";
177 0         0 next;
178             }
179              
180 5512         14320 $dict_const_name{$name}{$v_val} = $v_name;
181 5512         17177 $dict_const_value{$name}{$v_name} = $v_val;
182             }
183             elsif ($cmd eq 'vendor') {
184             # VENDOR Mikrotik 14988
185 70         226 $dict_vendor_name{ $name } = $id;
186 70         188 $dict_vendor_id{ $id } = $name;
187             }
188             elsif ($cmd eq 'begin-vendor') {
189             # BEGIN-VENDOR Huawei
190 36 50       105 if (! exists $dict_vendor_name{ $name }) {
191 0         0 warn "BEGIN-VENDOR $name - vendor id is unknown";
192             }
193             # set default vendor for all attributes below
194 36         62 $begin_vendor = $name;
195             }
196             elsif ($cmd eq 'end-vendor') {
197             # END-VENDOR Laurel
198 36 50       101 if (! $begin_vendor) {
199 0         0 warn "END-VENDOR found without BEGIN-VENDOR";
200 0         0 next;
201             }
202 36         59 $begin_vendor = undef;
203             }
204             elsif ($cmd eq 'begin-tlv') {
205 24 50       98 if ($begin_tlv) {
206             # no support for 2nd level
207 0         0 warn "Nested BEGIN-TLV found";
208             }
209              
210             # BEGIN-TLV WiMAX-PPAC
211             # must be defined attribute with type 'tlv' first
212 24 50       59 if (! exists $dict_attr{ $name }) {
213 0         0 warn "Begin-tlv for unknown attribute $name";
214 0         0 next;
215             }
216 24 50       69 if ($dict_attr{ $name }{type} ne 'tlv') {
217 0         0 warn "Begin-tlv for attribute $name of non-tlv type";
218 0         0 next;
219             }
220 24         43 $begin_tlv = $name;
221             }
222             elsif ($cmd eq 'end-tlv') {
223             # END-TLV WiMAX-PPAC
224 24 50       71 if (! $begin_tlv) {
225 0         0 warn "END-TLV found without BEGIN-TLV";
226 0         0 next;
227             }
228 24         67 $begin_tlv = undef;
229             }
230             elsif ($cmd eq '$include') {
231             # $INCLUDE mikrotik
232             # $INCLUDE /absolute/path/to/mikrotik
233              
234             # clear modifiers
235 96         175 ($begin_vendor, $begin_tlv) = ();
236              
237 96 100       727 if (File::Spec->file_name_is_absolute($name)) {
238 4         14 $self->_load_file($name);
239             }
240             else {
241 92         927 $self->_load_file( File::Spec->catfile($include_dir, $name) );
242             }
243             }
244             else {
245 0         0 warn "Unknown command: $cmd";
246             }
247              
248 10952         200807 return undef;
249             }
250              
251              
252             1;