File Coverage

blib/lib/Net/DNS/RR/SVCB.pm
Criterion Covered Total %
statement 179 179 100.0
branch 64 64 100.0
condition 13 13 100.0
subroutine 32 32 100.0
pod 6 11 100.0
total 294 299 100.0


line stmt bran cond sub pod time code
1             package Net::DNS::RR::SVCB;
2              
3 2     2   26 use strict;
  2         4  
  2         58  
4 2     2   10 use warnings;
  2         3  
  2         91  
5             our $VERSION = (qw$Id: SVCB.pm 1930 2023-08-21 14:10:10Z willem $)[2];
6              
7 2     2   32 use base qw(Net::DNS::RR);
  2         4  
  2         170  
8              
9              
10             =head1 NAME
11              
12             Net::DNS::RR::SVCB - DNS SVCB resource record
13              
14             =cut
15              
16 2     2   14 use integer;
  2         4  
  2         11  
17              
18 2     2   65 use Net::DNS::DomainName;
  2         4  
  2         63  
19 2     2   930 use Net::DNS::RR::A;
  2         5  
  2         53  
20 2     2   869 use Net::DNS::RR::AAAA;
  2         6  
  2         61  
21 2     2   890 use Net::DNS::Text;
  2         10  
  2         5132  
22              
23              
24             my %keybyname = (
25             mandatory => 'key0',
26             alpn => 'key1',
27             'no-default-alpn' => 'key2',
28             port => 'key3',
29             ipv4hint => 'key4',
30             ech => 'key5',
31             ipv6hint => 'key6',
32             dohpath => 'key7', # draft-schwartz-svcb-dns
33             ohttp => 'key8', # draft-pauly-ohai-svcb-config
34             );
35              
36              
37             sub _decode_rdata { ## decode rdata from wire-format octet string
38 14     14   32 my ( $self, $data, $offset ) = @_;
39              
40 14         37 my $rdata = substr $$data, $offset, $self->{rdlength};
41 14         36 $self->{SvcPriority} = unpack( 'n', $rdata );
42              
43 14         25 my $index;
44 14         37 ( $self->{TargetName}, $index ) = Net::DNS::DomainName->decode( \$rdata, 2 );
45              
46 14         39 my $params = $self->{SvcParams} = [];
47 14         20 my $limit = length($rdata) - 3;
48 14         29 while ( $index < $limit ) {
49 13         36 my ( $key, $size ) = unpack( "\@$index n2", $rdata );
50 13         30 push @$params, ( $key, substr $rdata, $index + 4, $size );
51 13         26 $index += ( $size + 4 );
52             }
53 14 100       32 die $self->type . ': corrupt RDATA' unless $index == length($rdata);
54 13         26 return;
55             }
56              
57              
58             sub _encode_rdata { ## encode rdata as wire-format octet string
59 12     12   21 my $self = shift;
60              
61 12         31 my @packed = pack 'n a*', $self->{SvcPriority}, $self->{TargetName}->encode;
62 12   100     45 my $params = $self->{SvcParams} || [];
63 12         24 my @params = @$params;
64 12         108 while (@params) {
65 5         11 my $key = shift @params;
66 5         68 my $val = shift @params;
67 5         25 push @packed, pack( 'n2a*', $key, length($val), $val );
68             }
69 12         41 return join '', @packed;
70             }
71              
72              
73             sub _format_rdata { ## format rdata portion of RR string.
74 22     22   31 my $self = shift;
75              
76 22         34 my $priority = $self->{SvcPriority};
77 22         53 my $target = $self->{TargetName}->string;
78 22   100     76 my $params = $self->{SvcParams} || [];
79 22 100       60 return ( $priority, $target ) unless scalar @$params;
80              
81 16         38 my $encode = $self->{TargetName}->encode();
82 16         31 my $length = 2 + length $encode;
83 16         78 my @target = grep {length} split /(\S{32})/, unpack 'H*', $encode;
  42         74  
84 16         47 my @rdata = unpack 'H4', pack 'n', $priority;
85 16         41 push @rdata, "\t; priority: $priority\n";
86 16         25 push @rdata, shift @target;
87 16         41 push @rdata, join '', "\t; target: ", substr( $target, 0, 50 ), "\n";
88 16         31 push @rdata, @target;
89              
90 16         36 my @params = @$params;
91 16         54 while (@params) {
92 31         49 my $key = shift @params;
93 31         45 my $val = shift @params;
94 31         42 push @rdata, "\n";
95 31 100       64 push @rdata, "; key$key=...\n" if $key > 15;
96 31         78 push @rdata, unpack 'H4H4', pack( 'n2', $key, length $val );
97 31         88 push @rdata, split /(\S{32})/, unpack 'H*', $val;
98 31         80 $length += 4 + length $val;
99             }
100 16         115 return ( "\\# $length", @rdata );
101             }
102              
103              
104             sub _parse_rdata { ## populate RR from rdata in argument list
105 32     32   88 my ( $self, @argument ) = @_;
106              
107 32         85 $self->svcpriority( shift @argument );
108 32         87 $self->targetname( shift @argument );
109              
110 32     1   183 local $SIG{__WARN__} = sub { die @_ };
  1         15  
111 32         83 while ( my $svcparam = shift @argument ) {
112 42         86 for ($svcparam) {
113 42         57 my @value;
114 42 100       198 if (/^key\d+=(.*)$/i) {
    100          
115 9 100       38 push @value, length($1) ? $1 : shift @argument;
116             } elsif (/^[^=]+=(.*)$/) {
117 21 100       70 local $_ = length($1) ? $1 : shift @argument;
118 21         48 s/^"([^"]*)"$/$1/; # strip enclosing quotes
119 21         64 push @value, split /,/;
120             } else {
121 12 100       40 push @value, '' unless $keybyname{lc $_}; # empty | Boolean
122             }
123              
124 42         84 s/[-]/_/g; # extract identifier
125 42         110 m/^([^=]+)/;
126 42         195 $self->$1(@value);
127             }
128             }
129 17         92 return;
130             }
131              
132              
133             sub _post_parse { ## parser post processing
134 30     30   50 my $self = shift;
135              
136 30   100     77 my $paramref = $self->{SvcParams} || [];
137 30 100       104 my %svcparam = scalar(@$paramref) ? @$paramref : return;
138              
139 22         121 $self->key0(undef); # ruse to force sorting of SvcParams
140 22 100       50 if ( defined $svcparam{0} ) {
141 6         10 my %unique;
142 6         16 foreach ( grep { !$unique{$_}++ } unpack 'n*', $svcparam{0} ) {
  9         32  
143 8 100       19 die( $self->type . qq[: unexpected "key0" in mandatory list] ) if $unique{0};
144 7 100       21 die( $self->type . qq[: duplicate "key$_" in mandatory list] ) if --$unique{$_};
145 6 100       16 die( $self->type . qq[: mandatory "key$_" not present] ) unless defined $svcparam{$_};
146             }
147 3         35 $self->mandatory( keys %unique ); # restore mandatory key list
148             }
149 19 100 100     67 die( $self->type . qq[: expected alpn="..." not present] ) if defined( $svcparam{2} ) && !$svcparam{1};
150 18         51 return;
151             }
152              
153              
154             sub _defaults { ## specify RR attribute default values
155 2     2   4 my $self = shift;
156              
157 2         9 $self->_parse_rdata(qw(0 .));
158 2         7 return;
159             }
160              
161              
162             sub svcpriority {
163 38     38 0 421 my ( $self, @value ) = @_; # uncoverable pod
164 38         76 for (@value) { $self->{SvcPriority} = 0 + $_ }
  35         106  
165 38   100     114 return $self->{SvcPriority} || 0;
166             }
167              
168              
169             sub targetname {
170 39     39 0 1395 my ( $self, @value ) = @_; # uncoverable pod
171              
172 39         63 for (@value) { $self->{TargetName} = Net::DNS::DomainName->new($_) }
  35         113  
173              
174 39 100       133 my $target = $self->{TargetName} ? $self->{TargetName}->name : return;
175 37 100       93 return $target unless $self->{SvcPriority};
176 31 100       84 return ( $target eq '.' ) ? $self->owner : $target;
177             }
178              
179              
180             sub mandatory { ## mandatory=key1,port,...
181 11     11 1 389 my ( $self, @value ) = @_;
182 11 100       22 my @list = map { $keybyname{lc $_} || $_ } map { split /,/ } @value;
  13         62  
  13         32  
183 11 100       21 my @keys = map { /(\d+)$/ ? $1 : die( $self->type . qq[: unexpected "$_"] ) } @list;
  13         64  
184 10         30 return $self->key0( _integer16( sort { $a <=> $b } @keys ) );
  4         17  
185             }
186              
187             sub alpn { ## alpn=h3,h2,...
188 5     5 1 386 my ( $self, @value ) = @_;
189 5         11 return $self->key1( _string(@value) );
190             }
191              
192             sub no_default_alpn { ## no-default-alpn (Boolean)
193 4     4 0 10 my ( $self, @value ) = @_; # uncoverable pod
194 4 100       25 return $self->key2( ( defined(wantarray) ? () : '' ), @value );
195             }
196              
197             sub port { ## port=1234
198 7     7 1 386 my ( $self, @value ) = @_;
199 7         18 return $self->key3( map { _integer16($_) } @value );
  6         13  
200             }
201              
202             sub ipv4hint { ## ipv4hint=192.0.2.1,...
203 3     3 1 352 my ( $self, @value ) = @_;
204 3         8 return $self->key4( _ipv4(@value) );
205             }
206              
207             sub ech { ## Format not specified
208 4     4 1 373 my ( $self, @value ) = @_;
209 4         22 return $self->key5(@value); # RESERVED
210             }
211              
212             sub ipv6hint { ## ipv6hint=2001:DB8::1,...
213 5     5 1 401 my ( $self, @value ) = @_;
214 5         13 return $self->key6( _ipv6(@value) );
215             }
216              
217             sub dohpath { ## dohpath=/dns-query{?dns}
218 2     2 0 367 my ( $self, @value ) = @_; # uncoverable pod
219 2         24 return $self->key7(@value);
220             }
221              
222             sub ohttp { ## ohttp (Boolean)
223 2     2 0 366 my ( $self, @value ) = @_; # uncoverable pod
224 2 100       16 return $self->key8( ( defined(wantarray) ? () : '' ), @value );
225             }
226              
227              
228             ########################################
229              
230              
231             sub _presentation { ## render octet string(s) in presentation format
232 58     58   122 my @arg = @_;
233 58 100       185 my $raw = scalar(@arg) ? join( '', @arg ) : return ();
234 50         157 return Net::DNS::Text->decode( \$raw, 0, length($raw) )->string;
235             }
236              
237             sub _integer16 {
238 16     16   29 my @arg = @_;
239 16         23 return _presentation( map { pack( 'n', $_ ) } @arg );
  18         95  
240             }
241              
242             sub _ipv4 {
243 3     3   7 my @arg = @_;
244 3         8 return _presentation( map { Net::DNS::RR::A::address( {}, $_ ) } @arg );
  1         5  
245             }
246              
247             sub _ipv6 {
248 5     5   11 my @arg = @_;
249 5         10 return _presentation( map { Net::DNS::RR::AAAA::address( {}, $_ ) } @arg );
  4         14  
250             }
251              
252             sub _string {
253 5     5   10 my @arg = @_;
254 5         13 local $_ = join ',', @arg; # reassemble argument string
255 5         8 s/\\,/\\044/g; # disguise (RFC1035) escaped comma
256 5 100       44 die <<"QQ" if /\\092,|\\092\\092/;
257             SVCB: Please use standard RFC1035 escapes
258             draft-ietf-dnsop-svcb-https double-escape nonsense not implemented
259             QQ
260 3         12 return _presentation( map { Net::DNS::Text->new($_)->encode() } split /,/ );
  2         5  
261             }
262              
263              
264             sub AUTOLOAD { ## Dynamic constructor/accessor methods
265 77     77   541 my ( $self, @argument ) = @_;
266              
267 77         99 our $AUTOLOAD;
268 77         274 my ($method) = reverse split /::/, $AUTOLOAD;
269              
270 77         173 my $super = "SUPER::$method";
271 77 100       387 return $self->$super(@argument) unless $method =~ /^key[0]*(\d+)$/i;
272 75         158 my $key = $1;
273              
274 75   100     230 my $paramsref = $self->{SvcParams} || [];
275 75         178 my %svcparams = @$paramsref;
276              
277 75 100       141 if ( scalar @argument ) {
278 60         90 my $arg = shift @argument; # keyNN($value);
279 60 100       133 delete $svcparams{$key} unless defined $arg;
280 60 100       136 die( $self->type . qq[: duplicate SvcParam "key$key"] ) if defined $svcparams{$key};
281 59 100       131 die( $self->type . qq[: invalid SvcParam "key$key"] ) if $key > 65534;
282 58 100       177 $svcparams{$key} = Net::DNS::Text->new("$arg")->raw if defined $arg;
283 58         235 $self->{SvcParams} = [map { ( $_, $svcparams{$_} ) } sort { $a <=> $b } keys %svcparams];
  125         321  
  130         201  
284 58 100       168 die( $self->type . qq[: unexpected number of values for "key$key"] ) if scalar @argument;
285             } else {
286 15 100       40 die( $self->type . qq[: no value specified for "key$key"] ) unless defined wantarray;
287             }
288              
289 64         103 my $value = $svcparams{$key};
290 64 100       191 return defined($value) ? _presentation($value) : $value;
291             }
292              
293             ########################################
294              
295              
296             1;
297             __END__