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