File Coverage

blib/lib/Proquint.pm
Criterion Covered Total %
statement 76 77 98.7
branch 16 26 61.5
condition 18 51 35.2
subroutine 13 13 100.0
pod 0 6 0.0
total 123 173 71.1


line stmt bran cond sub pod time code
1             package Proquint;
2 1     1   229890 use strict;
  1         2  
  1         30  
3 1     1   6 use warnings;
  1         2  
  1         22  
4 1     1   5 use Carp ();
  1         2  
  1         14  
5 1     1   1187 use Exporter::Tiny;
  1         3754  
  1         6  
6 1     1   722 use Socket (qw/inet_pton inet_ntop AF_INET AF_INET6/);
  1         3902  
  1         1030  
7              
8             our $VERSION = '1.0.0_2';
9             our @ISA = 'Exporter::Tiny';
10             our @EXPORT_OK = (
11             qw/
12             uint32proquint proquint32uint
13             hex2proquint proquint2hex
14             ip2proquint proquint2ip
15             /
16             );
17             our @EXPORT_TAGS = ( all => \@EXPORT_OK );
18              
19             my @UINT_TO_CONSONANT = (qw/ b d f g h j k l m n p r s t v z /);
20             my @UINT_TO_VOWEL = (qw/ a i o u /);
21             my $CHARS_PER_CHUNK = 5;
22             my $MASK_LAST2 = 0x3;
23             my $MASK_LAST4 = 0xF;
24             my $SEPARATOR = '-';
25              
26             my %CONSONANT_TO_UINT = do {
27             my $i = 0;
28             map { $_ => $i++ } @UINT_TO_CONSONANT;
29             };
30              
31             my %VOWEL_TO_UINT = do {
32             my $i = 0;
33             map { $_ => $i++ } @UINT_TO_VOWEL;
34             };
35              
36             sub _uint16_to_chunk {
37 58   33 58   118 my $in = shift // Carp::croak 'usage: _uint16_to_chunk($INTEGER)';
38 58         80 my $out = '';
39              
40 58         104 foreach my $i ( 1 .. $CHARS_PER_CHUNK ) {
41 290 100       414 if ( $i & 1 ) {
42 174         274 $out .= $UINT_TO_CONSONANT[ $in & $MASK_LAST4 ];
43 174         237 $in >>= 4;
44             }
45             else {
46 116         158 $out .= $UINT_TO_VOWEL[ $in & $MASK_LAST2 ];
47 116         148 $in >>= 2;
48             }
49             }
50 58         252 scalar reverse $out;
51             }
52              
53             sub _chunk_to_uint16 {
54 58   33 58   113 my $in = shift // Carp::croak 'usage: _chunk_to_uint16($INTEGER)';
55              
56 58 50       104 Carp::croak 'invalid chunk: ' . $in unless length($in) == $CHARS_PER_CHUNK;
57              
58 58         67 my $res = 0;
59 58         147 foreach my $c ( split //, $in ) {
60 290 100       466 if ( exists $CONSONANT_TO_UINT{$c} ) {
    50          
61 174         202 $res <<= 4;
62 174         255 $res += $CONSONANT_TO_UINT{$c};
63             }
64             elsif ( exists $VOWEL_TO_UINT{$c} ) {
65 116         135 $res <<= 2;
66 116         160 $res += $VOWEL_TO_UINT{$c};
67             }
68             else {
69 0         0 Carp::croak 'invalid quint char: ' . $c;
70             }
71             }
72              
73 58         219 $res;
74             }
75              
76             # uint32proquint(0x7f000001) eq 'lusab-babad';
77             sub uint32proquint {
78 2   33 2 0 8318 my $in = shift // Carp::croak 'usage: uint32proquint($INTEGER)';
79 2   33     10 my $sep = shift // $SEPARATOR;
80              
81 2 50 33     9 Carp::croak('input out of range 0-0xFFFFFFFF')
82             if $in < 0 or $in > 0xffffffff;
83              
84 2         8 _uint16_to_chunk( $in >> 16 ) . $sep . _uint16_to_chunk($in);
85             }
86              
87             # proquint32uint('lusab-babad') == 0x7f000001;
88             sub proquint32uint {
89 2   33 2 0 7 my $in = shift // Carp::croak 'usage: proquint32uint($QUINT)';
90 2   33     9 my $sep = shift // $SEPARATOR;
91              
92 2         31 $in =~ s/$sep//g;
93 2 50       11 Carp::croak 'invalid quint: ' . $in
94             unless not length($in) % $CHARS_PER_CHUNK;
95              
96 2         23 my @chunks = $in =~ m/(.{$CHARS_PER_CHUNK})/gx;
97 2 50       8 Carp::croak 'invalid quint: ' . $in unless @chunks == 2;
98              
99 2         9 my $out = _chunk_to_uint16( $chunks[0] );
100 2         4 $out <<= 16;
101 2         5 $out += _chunk_to_uint16( $chunks[1] );
102 2         27 $out;
103             }
104              
105             # hex2proquint('7f00001') eq 'lusab-babad'
106             sub hex2proquint {
107 6   33 6 0 10572 my $in = shift // Carp::croak 'usage: hex2proquint($HEXIDECIMAL)';
108 6   33     23 my $sep = shift // $SEPARATOR;
109              
110 6         23 $in =~ s/^0[xX]//;
111              
112 6 50       18 Carp::croak 'input must be multiple of 4-characters'
113             unless not length($in) % 4;
114              
115             join( $sep,
116 6         33 map { _uint16_to_chunk( hex( '0x' . $_ ) ) } $in =~ m/(.{4})/g );
  12         32  
117             }
118              
119             # proquint2hex('lusab-babad') eq '7f000001';
120             sub proquint2hex {
121 6   33 6 0 20 my $in = shift // Carp::croak 'usage: proquint2hex($QUINT)';
122 6   33     21 my $sep = shift // $SEPARATOR;
123              
124 6         37 $in =~ s/$sep//g;
125 6 50       18 Carp::croak 'invalid quint: ' . $in
126             unless not length($in) % $CHARS_PER_CHUNK;
127              
128 6         44 my @chunks = $in =~ m/(.{$CHARS_PER_CHUNK})/g;
129 6 50       14 Carp::croak 'invalid quint: ' . $in unless @chunks;
130              
131 6         13 join( '', map { sprintf( '%04x', _chunk_to_uint16($_) ) } @chunks );
  12         20  
132             }
133              
134             # ip2proquint('127.0.0.1') eq 'lusab-babad'
135             sub ip2proquint {
136 15   33 15 0 26349 my $in = shift // Carp::croak 'usage: ip2proquint($ADDRESS)';
137 15   33     54 my $sep = shift // $SEPARATOR;
138              
139 15   66     122 my $ip = inet_pton( AF_INET6, $in ) // inet_pton( AF_INET, $in )
      33        
140             // Carp::croak sprintf q{invalid IP address '%s'}, $in;
141              
142 15         58 join( $sep, map { _uint16_to_chunk($_) } unpack 'n*', $ip );
  42         69  
143             }
144              
145             # proquint2ip('lusab-babad') eq '127.0.0.1'
146             sub proquint2ip {
147 15   33 15 0 69 my $in = shift // Carp::croak 'usage: proquint2ip($ADDRESS)';
148 15   33     58 my $sep = shift // $SEPARATOR;
149              
150 15         89 $in =~ s/$sep//g;
151 15 50       51 Carp::croak 'invalid quint: ' . $in
152             unless not length($in) % $CHARS_PER_CHUNK;
153              
154 15         114 my @chunks = $in =~ m/(.{$CHARS_PER_CHUNK})/g;
155 15 50       35 Carp::croak 'invalid quint: ' . $in unless @chunks;
156              
157 15         32 my $ip = pack 'n*', map { _chunk_to_uint16($_) } @chunks;
  42         77  
158 15 100       146 return inet_ntop( 2 == @chunks ? AF_INET : AF_INET6, $ip );
159             }
160              
161             1;
162              
163             __END__