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