File Coverage

blib/lib/DNS/Bananafonana.pm
Criterion Covered Total %
statement 90 103 87.3
branch 26 38 68.4
condition 8 15 53.3
subroutine 11 11 100.0
pod 0 3 0.0
total 135 170 79.4


line stmt bran cond sub pod time code
1             package DNS::Bananafonana;
2              
3 1     1   121641 use strict;
  1         3  
  1         48  
4 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $bananafonana_strings);
  1         1  
  1         211  
5              
6 1     1   18 use Carp;
  1         18  
  1         85  
7 1     1   7 use Exporter;
  1         2  
  1         48  
8 1     1   7 use Math::BigInt qw(:constant);
  1         2  
  1         10  
9              
10             $VERSION = '0.1';
11              
12             @ISA = qw(Math::BigInt);
13             @EXPORT = qw();
14             @EXPORT_OK = qw(from_bananafonana to_bananafonana bananafonana);
15              
16             =head1 NAME
17              
18             DNS::Bananafonana - Perl extension for Bananafonana encoding / decoding
19              
20             =head1 SYNOPSIS
21              
22             use DNS::Bananafonana;
23              
24             $bigint = from_bananafonana($number);
25             $bafostr = to_bananafonana($bigint);
26             $hostname = bananafonana($ptr, $domain, $prefix);
27             $ip = bananafonana($hostname, $domain, $prefix);
28              
29             =head1 DESCRIPTION
30              
31             RFC 1924 describes a compact, fixed-size representation of IPv6
32             addresses which uses a base 85 number system. The base 85 numbers
33             (from 0 to 84) are as follows:
34              
35             0..9 A..Z a..z ! # $ % & ( ) * + - ; < = > ? @ ^ _ ` { | } ~
36              
37             In order to let human beings pronounce the resulting string more easily and
38             to be able to use base 85 encoding in DNS naming schemes, an alternative
39             encoding scheme is used, based on 85 consonant-vowel pairs, as suggested by
40             DGolden on Slashdot
41             (http://tech.slashdot.org/comments.pl?sid=649579&cid=24654733).
42              
43             This module has a variable called C<$DNS::Bananafonana::bananafonana_strings>,
44             which is a string containing the 85 two-character strings that make up
45             the bananafonana "alphabet" from lowest to highest , in that order.
46              
47             Additionally, the following three functions are defined for general
48             use. (They will be exported upon request.)
49              
50             =cut
51              
52             $DNS::Bananafonana::bananafonana_strings =
53             "babebibobudadedidodufafefifofugagegigogu" .
54             "hahehihohujajejijojukakekikokulalelilolu" .
55             "mamemimomunaneninonupapepipopusasesisosu" .
56             "tatetitotuvavevivovuxaxexixoxuwawewiwowu" .
57             "zazezizozu";
58              
59              
60             # Maybe we can make this a little more general...
61              
62 1     1   679 use constant BAFO_BASE => 85;
  1         2  
  1         58  
63 1     1   45 use constant BAFO_BLOCKSIZE => 5;
  1         2  
  1         40  
64 1     1   4 use constant BAFO_MARKUP => '-';
  1         2  
  1         140  
65              
66             =pod
67              
68             =head1 from_bananafonana
69              
70             =head2 Parameters
71              
72             A string composed of valid bananafonana strings.
73              
74             =head2 Returns
75              
76             A C object representing the number.
77              
78             =cut
79              
80             sub from_bananafonana
81             {
82 6     6 0 1208 my $num = shift;
83             # Remove markup characters
84 6         19 $num =~ s/[-_\.]//g;
85 6         20 my $answer = new Math::BigInt "0";
86 6         426 my $n;
87             my $d;
88 6         19 while (length($d = substr($num,0,2)) > 0) {
89 67 50       14439 if (length($d) == 1) {
90 0         0 croak __PACKAGE__ . "::from_bananafonana -- invalid bananafonana string $d";
91             }
92 67         5242 $num = substr($num,2);
93 67         1054 $answer = $answer * BAFO_BASE;
94 67         4798 $n = index($bananafonana_strings, $d)/2.0;
95 67 100       167 if ($n < 0.0) {
96 2         422 croak __PACKAGE__ . "::from_bananafonana -- invalid bananafonana string $d";
97             }
98 65         162 $answer = $answer + $n;
99             }
100 4         1107 return $answer;
101             }
102              
103             =pod
104              
105             =head1 to_bananafonana
106              
107             =head2 Parameters
108              
109             A C object.
110              
111             Optionally:
112              
113             A markup character to split the string in more readable parts.
114             Can be C<->, C<_> or C<.>. Defaults to C<->.
115              
116             A blocksize that determines the number of consonant-vowel combinations
117             between each markup character. Defaults to 5.
118              
119             =head2 Returns
120              
121             A string of bananafonana strings representing the number.
122              
123             =cut
124              
125             sub to_bananafonana
126             {
127 4     4 0 810 my $num = shift;
128 4   50     32 my $markup = shift || BAFO_MARKUP;
129 4   50     28 my $blocksize = (shift || BAFO_BLOCKSIZE) + 1;
130 4         377 my @digits;
131             my $q;
132 0         0 my $r;
133 0         0 my $d;
134              
135 4 50       17 if (! $markup =~ /[-_\.]/) {
136 0         0 croak __PACKAGE__ . "::to_bananafonana -- invalid markup character ($markup)";
137             }
138 4 50       132 if ($blocksize < 0) {
139 0         0 croak __PACKAGE__ . "::to_bananafonana -- invalid blocksize ($blocksize)";
140             }
141 4 100       164 if ($num eq "NaN" ) {
142 1         20 croak __PACKAGE__ . "::to_bananafonana -- invalid number ($num)";
143             }
144 3         123 while ($num > 0) {
145 45         1137 $q = $num / BAFO_BASE;
146 45         3779 $r = $num % BAFO_BASE;
147 45         3806 $d = substr($bananafonana_strings, $r*2, 2);
148 45 100 66     3987 if ($blocksize > 0 && (($#digits + 1) % $blocksize == 0)) {
149 9         2191 unshift @digits, $markup;
150             }
151 45         8644 unshift @digits, $d;
152 45         164 $num = $q;
153             }
154 3         75 pop @digits;
155 3 50       13 unshift @digits, 'ba' unless (@digits);
156 3         34 return join('', @digits);
157             }
158              
159             =pod
160              
161             =head1 bananafonana
162              
163             =head2 Parameters
164              
165             A string
166              
167             A string containing the domain name of the record. It will be appended to the
168             result when a pointer record is asked and removed from the input for hostname
169             lookups.
170              
171             Optionally:
172              
173             A string containing a prefix that needs to be added before the bananafonana
174             representation of the ip address. Defaults to empty.
175              
176             A markup character to split the string in more readable parts.
177             Can be C<->, C<_> or C<.>. Defaults to C<->.
178              
179             A blocksize that determines the number of consonant-vowel combinations
180             between each markup character. Defaults to 5.
181              
182             =head2 Returns
183              
184             A string containing either the bananafonana representation of the ip address
185             (presented in 1.2.3.4.in-addr.arpa or a.b...e.f.ip6.arpa notation)
186             or a string representing the ip address determined from the bonananfonana
187             encoded hostname (for all domains not ending in in-addr|ip6.arpa).
188              
189             =cut
190              
191             sub bananafonana
192             {
193 5     5 0 646 my $name = shift;
194 5   50     16 my $domain = shift || "";
195 5   50     12 my $prefix = shift || "";
196 5   50     26 my $markup = shift || BAFO_MARKUP;
197 5   50     32 my $blocksize = (shift || BAFO_BLOCKSIZE) + 1;
198 5         436 my $ip;
199              
200             # Input validation
201 5 50       17 if (! $markup =~ /[-_\.]/) {
202 0         0 croak __PACKAGE__ .
203             "::bananafonana -- invalid markup character($markup)";
204             }
205 5 50       15 if ($blocksize < 0) {
206 0         0 croak __PACKAGE__ .
207             "::bananafonana -- invalid blocksize ($blocksize)";
208             }
209 5 50       145 if ($domain eq "" ) {
210 0         0 croak __PACKAGE__ . "::bananafonana -- empty domain is not allowed";
211             }
212             # Strip leading and trailing dots from domain
213 5         45 $domain =~ s/^\.|\.$//g;
214              
215             # IPv4 PTR record
216 5 100       90 if ($name =~ /^(.*)\.in-addr\.arpa[.]{0,1}$/) {
    100          
    50          
217 1         3 $ip = eval { $prefix.to_bananafonana(
  1         15  
218             new Math::BigInt("0x".sprintf("%02x%02x%02x%02x",
219             reverse split(/\./, $1)))
220             ).".".$domain; };
221 1 50       6 if (not defined($ip)) {
222 0         0 croak __PACKAGE__ . "::bananafonana -- cannot encode $1";
223             }
224 1         5 return($ip);
225              
226             # IPv6 PTR record
227             } elsif ($name =~ /^(.*)\.ip6\.arpa[.]{0,1}$/) {
228 2         4 $ip = eval { $prefix.to_bananafonana(
  2         29  
229             new Math::BigInt("0x".join('', reverse split(/\./, $1)))
230             ).".".$domain; };
231 2 100       292 if (not defined($ip)) {
232 1         150 croak __PACKAGE__ . "::bananafonana -- cannot encode $1";
233             }
234 1         9 return($ip);
235              
236             } elsif ($name =~ /^$prefix(.*)\.$domain[.]{0,1}$/) {
237              
238             # A or AAAA record
239 2         5 $name = $1;
240 2         9 $name =~ s/[-_\.]//g;
241 2 100       7 if (length($name) == 10) {
242              
243             # A record (Note: this also incorrectly matches ::abcd IPv6 addresses!)
244 1         84 $ip = eval { sprintf("%08x",from_bananafonana($name)); };
  1         4  
245 1 50       20 if (not defined($ip)) {
246 0         0 croak __PACKAGE__ . "::bananafonana -- cannot decode $name";
247             }
248 1         4 return(sprintf("%d.%d.%d.%d",
249             hex(substr($ip,0,2)), hex(substr($ip,2,2)),
250             hex(substr($ip,4,2)), hex(substr($ip,6,2))));
251             } else {
252              
253             # AAAA record
254 1         80 $ip = eval { from_bananafonana($name)->as_hex(); };
  1         4  
255 1 50       371 if (not defined($ip)) {
256 0         0 croak __PACKAGE__ . "::bananafonana -- cannot decode $name";
257             }
258 1 50       5 if (length($ip) < 34) {
259 0         0 $ip = "0x".substr('00000000000000000000000000000000',0,
260             34-length($ip)).substr($ip,2);
261             }
262 1         91 $name = sprintf("%x:%x:%x:%x:%x:%x:%x:%x",
263             hex(substr($ip,2,4)), hex(substr($ip,6,4)),
264             hex(substr($ip,10,4)), hex(substr($ip,14,4)),
265             hex(substr($ip,18,4)), hex(substr($ip,22,4)),
266             hex(substr($ip,26,4)), hex(substr($ip,30,4)));
267 1         231 $name =~ s/^(0:)+|(:0)+$|(:0)+:/::/;
268 1         8 return($name);
269             }
270              
271             } else {
272              
273             #Invalid question
274 0           croak __PACKAGE__ . "::bananafonana -- invalid input ($name)";
275              
276             }
277             }
278              
279             =head1 BUGS
280              
281             The bananafonana function is currently decoding all hostnames with 10
282             character bonanafonana encoded addresses as IPv4 addresses. This prevends the
283             correct encoding and decoding for IPv6 addresses in the range ::/96, which
284             should not be a big limitation in practice.
285              
286             =head1 AUTHOR
287              
288             Michiel Fokke
289             Based upon work from Tony Monroe
290              
291             =head1 SEE ALSO
292              
293             perl(1).
294              
295             =cut
296              
297             1;
298             __END__