File Coverage

blib/lib/Net/IP/Match/Trie/PP.pm
Criterion Covered Total %
statement 86 86 100.0
branch 10 12 83.3
condition 2 5 40.0
subroutine 13 13 100.0
pod 2 9 22.2
total 113 125 90.4


line stmt bran cond sub pod time code
1             # -*- mode: coding: utf-8; -*-
2             package Net::IP::Match::Trie;
3              
4 1     1   6097 use strict;
  1         4  
  1         114  
5 1     1   6 use warnings;
  1         1  
  1         43  
6 1     1   5 no warnings qw(redefine);
  1         2  
  1         41  
7              
8 1     1   1187 use Socket qw(inet_aton);
  1         4179  
  1         1042  
9              
10             our $VERSION = '1.00';
11              
12             our $CIDR_TABLE_BITS = 8;
13             our $CIDR_TABLE_SIZE = (1 << $CIDR_TABLE_BITS);
14              
15             # helper
16             sub itonetmask($) {
17 6     6 0 9     my($n, $netmask) = @_;
18              
19 6 50 33     36     return () if ($n < 0 || 32 < $n);
20              
21 6         9     my $m = 1 << (32 - $n);
22 6         7     --$m;
23 6         9     $netmask = ~$m;
24 6         11     return $netmask & 0xFFFFFFFF;
25             }
26              
27             sub is_leaf($) {
28 262175     262175 0 288967     my($pt) = @_;
29 262175         650563     return $pt->{child}[0] == $pt;
30             }
31              
32             sub new_trie_node() {
33 14     14 0 68     my $node = { name => "", bits => 0, child => [] };
34 14         36     for (my $i = 0; $i < $CIDR_TABLE_SIZE; $i++) {
35 3584         6390         $node->{child}[$i] = $node;
36                 }
37 14         26     return $node;
38             }
39              
40             sub digg_trie($) {
41 6     6 0 7     my($child) = @_;
42 6         12     my $parent = new_trie_node;
43 6         19     for (my $i = 0; $i < $CIDR_TABLE_SIZE; $i++) {
44 1536         2708         $parent->{child}[$i] = $child;
45                 }
46 6         13     return $parent;
47             }
48              
49             sub update_leaf($$) {
50 1024     1024 0 3743     my($pt, $leaf) = @_;
51 1024         1005     my $used = 0;
52              
53 1024         2000     for (my $i = 0; $i < $CIDR_TABLE_SIZE; $i++) {
54 262144         340367         my $next = $pt->{child}[$i];
55 262144 100       390000         if (is_leaf($next)) {
56 261376 100       906956             if ($next->{bits} < $leaf->{bits}) {
57 1003         1063                 $pt->{child}[$i] = $leaf;
58 1003         2118                 $used = 1;
59                         }
60                     } else {
61 768         1511             $used |= &update_leaf($next, $leaf);
62                     }
63                 }
64              
65 1024         3555     return $used;
66             }
67              
68              
69             sub new {
70 1     1 0 9     my($class, %opt) = @_;
71              
72 1         3     my $self = bless {
73                    }, $class;
74              
75 1         3     my $root = new_trie_node;
76 1         2     $root->{name} = "R";
77 1         3     my $nullnode = new_trie_node;
78 1         4     for (my $i=0; $i < $CIDR_TABLE_SIZE; $i++) {
79 256         392         $root->{child}[$i] = $nullnode;
80                 }
81 1         9     $self->{root} = $root;
82              
83 1         4     return $self;
84             }
85              
86             # name => [ cidr1, cidr2, ... ]
87             sub add {
88 4     4 1 35     my($self, $name, $cidrs) = @_;
89              
90 4         4     my $ad;
91 4         4     my $nm = 0xFFFFFFFF;
92              
93             ### name: $name
94 4         8     for my $cidr (@$cidrs) {
95 6         22         my($ip, $len) = split m{/}, $cidr, 2;
96 6   50     17         $len ||= 32;
97             ### cidr, ip, len: join ', ', $cidr, $ip, $len
98              
99 6         52         $ad = unpack "N", inet_aton($ip);
100 6         15         $nm = itonetmask($len);
101             ### ad : sprintf "%08X", $ad
102             ### nm : sprintf "%08X", $nm
103              
104 6         8         $ad = $ad & ($nm & 0xFFFFFFFF);
105             ### ad&nm: sprintf "%08X", $ad
106              
107 6         10         my $pt = $self->{root};
108 6         21         my $p_leaf = new_trie_node;
109              
110 6         11         $p_leaf->{name} = $name;
111 6         11         $p_leaf->{bits} = $len;
112              
113 6         16         while ($len > $CIDR_TABLE_BITS) {
114             ### ad : sprintf "%08X", $ad
115 10         15             my $b = $ad >> (32 - $CIDR_TABLE_BITS);
116             ### b: $b
117 10         13             my $next = $pt->{child}[$b];
118 10 100       17             if (is_leaf($next)) {
119 6         8                 $pt->{child}[$b] = $next = digg_trie($next);
120                         }
121 10         15             $pt = $next;
122 10         13             $ad = $ad << $CIDR_TABLE_BITS & 0xFFFFFFFF;
123 10         23             $len -= $CIDR_TABLE_BITS;
124                     }
125              
126                     {
127 6         7             my $bmin = $ad >> (32 - $CIDR_TABLE_BITS);
  6         9  
128 6         9             my $bmax = $bmin + (1 << ($CIDR_TABLE_BITS - $len));
129 6         6             my $used = 0;
130 6         13             for (my $i = $bmin; $i < $bmax; $i++) {
131 21         24                 my $target = $pt->{child}[$i];
132 21 100       27                 if (is_leaf($target)) {
133 20 50       42                     if ($target->{bits} < $p_leaf->{bits}) {
134 20         22                         $pt->{child}[$i] = $p_leaf;
135 20         55                         $used = 1;
136                                 }
137                             } else {
138 1         3                     for (my $j = 0; $j < $CIDR_TABLE_SIZE; $j++) {
139 256         726                         $used |= update_leaf($target, $p_leaf);
140                                 }
141                             }
142                         }
143                     }
144                 }
145             }
146              
147             sub match_ip {
148 10     10 1 15746     my($self, $ip) = @_;
149              
150 10         45     my @addrs = split /\./, $ip, 4;
151 10         114     return $self->{root}{child}[$addrs[0]]->{child}[$addrs[1]]->{child}[$addrs[2]]->{child}[$addrs[3]]->{name};
152             }
153              
154             sub impl {
155 1     1 0 8190     my($self) = @_;
156 1         7     return "PP";
157             }
158              
159             1;
160