1package dns
2
3import (
4	"bytes"
5	"encoding/binary"
6	"errors"
7	"net"
8	"sort"
9	"strconv"
10	"strings"
11)
12
13// SVCBKey is the type of the keys used in the SVCB RR.
14type SVCBKey uint16
15
16// Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2.
17const (
18	SVCB_MANDATORY       SVCBKey = 0
19	SVCB_ALPN            SVCBKey = 1
20	SVCB_NO_DEFAULT_ALPN SVCBKey = 2
21	SVCB_PORT            SVCBKey = 3
22	SVCB_IPV4HINT        SVCBKey = 4
23	SVCB_ECHCONFIG       SVCBKey = 5
24	SVCB_IPV6HINT        SVCBKey = 6
25	svcb_RESERVED        SVCBKey = 65535
26)
27
28var svcbKeyToStringMap = map[SVCBKey]string{
29	SVCB_MANDATORY:       "mandatory",
30	SVCB_ALPN:            "alpn",
31	SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
32	SVCB_PORT:            "port",
33	SVCB_IPV4HINT:        "ipv4hint",
34	SVCB_ECHCONFIG:       "echconfig",
35	SVCB_IPV6HINT:        "ipv6hint",
36}
37
38var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
39
40func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey {
41	n := make(map[string]SVCBKey, len(m))
42	for u, s := range m {
43		n[s] = u
44	}
45	return n
46}
47
48// String takes the numerical code of an SVCB key and returns its name.
49// Returns an empty string for reserved keys.
50// Accepts unassigned keys as well as experimental/private keys.
51func (key SVCBKey) String() string {
52	if x := svcbKeyToStringMap[key]; x != "" {
53		return x
54	}
55	if key == svcb_RESERVED {
56		return ""
57	}
58	return "key" + strconv.FormatUint(uint64(key), 10)
59}
60
61// svcbStringToKey returns the numerical code of an SVCB key.
62// Returns svcb_RESERVED for reserved/invalid keys.
63// Accepts unassigned keys as well as experimental/private keys.
64func svcbStringToKey(s string) SVCBKey {
65	if strings.HasPrefix(s, "key") {
66		a, err := strconv.ParseUint(s[3:], 10, 16)
67		// no leading zeros
68		// key shouldn't be registered
69		if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" {
70			return svcb_RESERVED
71		}
72		return SVCBKey(a)
73	}
74	if key, ok := svcbStringToKeyMap[s]; ok {
75		return key
76	}
77	return svcb_RESERVED
78}
79
80func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
81	l, _ := c.Next()
82	i, e := strconv.ParseUint(l.token, 10, 16)
83	if e != nil || l.err {
84		return &ParseError{l.token, "bad SVCB priority", l}
85	}
86	rr.Priority = uint16(i)
87
88	c.Next()        // zBlank
89	l, _ = c.Next() // zString
90	rr.Target = l.token
91
92	name, nameOk := toAbsoluteName(l.token, o)
93	if l.err || !nameOk {
94		return &ParseError{l.token, "bad SVCB Target", l}
95	}
96	rr.Target = name
97
98	// Values (if any)
99	l, _ = c.Next()
100	var xs []SVCBKeyValue
101	// Helps require whitespace between pairs.
102	// Prevents key1000="a"key1001=...
103	canHaveNextKey := true
104	for l.value != zNewline && l.value != zEOF {
105		switch l.value {
106		case zString:
107			if !canHaveNextKey {
108				// The key we can now read was probably meant to be
109				// a part of the last value.
110				return &ParseError{l.token, "bad SVCB value quotation", l}
111			}
112
113			// In key=value pairs, value does not have to be quoted unless value
114			// contains whitespace. And keys don't need to have values.
115			// Similarly, keys with an equality signs after them don't need values.
116			// l.token includes at least up to the first equality sign.
117			idx := strings.IndexByte(l.token, '=')
118			var key, value string
119			if idx < 0 {
120				// Key with no value and no equality sign
121				key = l.token
122			} else if idx == 0 {
123				return &ParseError{l.token, "bad SVCB key", l}
124			} else {
125				key, value = l.token[:idx], l.token[idx+1:]
126
127				if value == "" {
128					// We have a key and an equality sign. Maybe we have nothing
129					// after "=" or we have a double quote.
130					l, _ = c.Next()
131					if l.value == zQuote {
132						// Only needed when value ends with double quotes.
133						// Any value starting with zQuote ends with it.
134						canHaveNextKey = false
135
136						l, _ = c.Next()
137						switch l.value {
138						case zString:
139							// We have a value in double quotes.
140							value = l.token
141							l, _ = c.Next()
142							if l.value != zQuote {
143								return &ParseError{l.token, "SVCB unterminated value", l}
144							}
145						case zQuote:
146							// There's nothing in double quotes.
147						default:
148							return &ParseError{l.token, "bad SVCB value", l}
149						}
150					}
151				}
152			}
153			kv := makeSVCBKeyValue(svcbStringToKey(key))
154			if kv == nil {
155				return &ParseError{l.token, "bad SVCB key", l}
156			}
157			if err := kv.parse(value); err != nil {
158				return &ParseError{l.token, err.Error(), l}
159			}
160			xs = append(xs, kv)
161		case zQuote:
162			return &ParseError{l.token, "SVCB key can't contain double quotes", l}
163		case zBlank:
164			canHaveNextKey = true
165		default:
166			return &ParseError{l.token, "bad SVCB values", l}
167		}
168		l, _ = c.Next()
169	}
170	rr.Value = xs
171	if rr.Priority == 0 && len(xs) > 0 {
172		return &ParseError{l.token, "SVCB aliasform can't have values", l}
173	}
174	return nil
175}
176
177// makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys.
178func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
179	switch key {
180	case SVCB_MANDATORY:
181		return new(SVCBMandatory)
182	case SVCB_ALPN:
183		return new(SVCBAlpn)
184	case SVCB_NO_DEFAULT_ALPN:
185		return new(SVCBNoDefaultAlpn)
186	case SVCB_PORT:
187		return new(SVCBPort)
188	case SVCB_IPV4HINT:
189		return new(SVCBIPv4Hint)
190	case SVCB_ECHCONFIG:
191		return new(SVCBECHConfig)
192	case SVCB_IPV6HINT:
193		return new(SVCBIPv6Hint)
194	case svcb_RESERVED:
195		return nil
196	default:
197		e := new(SVCBLocal)
198		e.KeyCode = key
199		return e
200	}
201}
202
203// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01).
204type SVCB struct {
205	Hdr      RR_Header
206	Priority uint16
207	Target   string         `dns:"domain-name"`
208	Value    []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is zero.
209}
210
211// HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
212// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
213type HTTPS struct {
214	SVCB
215}
216
217func (rr *HTTPS) String() string {
218	return rr.SVCB.String()
219}
220
221func (rr *HTTPS) parse(c *zlexer, o string) *ParseError {
222	return rr.SVCB.parse(c, o)
223}
224
225// SVCBKeyValue defines a key=value pair for the SVCB RR type.
226// An SVCB RR can have multiple SVCBKeyValues appended to it.
227type SVCBKeyValue interface {
228	Key() SVCBKey          // Key returns the numerical key code.
229	pack() ([]byte, error) // pack returns the encoded value.
230	unpack([]byte) error   // unpack sets the value.
231	String() string        // String returns the string representation of the value.
232	parse(string) error    // parse sets the value to the given string representation of the value.
233	copy() SVCBKeyValue    // copy returns a deep-copy of the pair.
234	len() int              // len returns the length of value in the wire format.
235}
236
237// SVCBMandatory pair adds to required keys that must be interpreted for the RR
238// to be functional.
239// Basic use pattern for creating a mandatory option:
240//
241//	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
242//	e := new(dns.SVCBMandatory)
243//	e.Code = []uint16{65403}
244//	s.Value = append(s.Value, e)
245type SVCBMandatory struct {
246	Code []SVCBKey // Must not include mandatory
247}
248
249func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
250
251func (s *SVCBMandatory) String() string {
252	str := make([]string, len(s.Code))
253	for i, e := range s.Code {
254		str[i] = e.String()
255	}
256	return strings.Join(str, ",")
257}
258
259func (s *SVCBMandatory) pack() ([]byte, error) {
260	codes := append([]SVCBKey(nil), s.Code...)
261	sort.Slice(codes, func(i, j int) bool {
262		return codes[i] < codes[j]
263	})
264	b := make([]byte, 2*len(codes))
265	for i, e := range codes {
266		binary.BigEndian.PutUint16(b[2*i:], uint16(e))
267	}
268	return b, nil
269}
270
271func (s *SVCBMandatory) unpack(b []byte) error {
272	if len(b)%2 != 0 {
273		return errors.New("dns: svcbmandatory: value length is not a multiple of 2")
274	}
275	codes := make([]SVCBKey, 0, len(b)/2)
276	for i := 0; i < len(b); i += 2 {
277		// We assume strictly increasing order.
278		codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:])))
279	}
280	s.Code = codes
281	return nil
282}
283
284func (s *SVCBMandatory) parse(b string) error {
285	str := strings.Split(b, ",")
286	codes := make([]SVCBKey, 0, len(str))
287	for _, e := range str {
288		codes = append(codes, svcbStringToKey(e))
289	}
290	s.Code = codes
291	return nil
292}
293
294func (s *SVCBMandatory) len() int {
295	return 2 * len(s.Code)
296}
297
298func (s *SVCBMandatory) copy() SVCBKeyValue {
299	return &SVCBMandatory{
300		append([]SVCBKey(nil), s.Code...),
301	}
302}
303
304// SVCBAlpn pair is used to list supported connection protocols.
305// Protocol ids can be found at:
306// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
307// Basic use pattern for creating an alpn option:
308//
309//	h := new(dns.HTTPS)
310//	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
311//	e := new(dns.SVCBAlpn)
312//	e.Alpn = []string{"h2", "http/1.1"}
313//	h.Value = append(o.Value, e)
314type SVCBAlpn struct {
315	Alpn []string
316}
317
318func (*SVCBAlpn) Key() SVCBKey     { return SVCB_ALPN }
319func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") }
320
321func (s *SVCBAlpn) pack() ([]byte, error) {
322	// Liberally estimate the size of an alpn as 10 octets
323	b := make([]byte, 0, 10*len(s.Alpn))
324	for _, e := range s.Alpn {
325		if e == "" {
326			return nil, errors.New("dns: svcbalpn: empty alpn-id")
327		}
328		if len(e) > 255 {
329			return nil, errors.New("dns: svcbalpn: alpn-id too long")
330		}
331		b = append(b, byte(len(e)))
332		b = append(b, e...)
333	}
334	return b, nil
335}
336
337func (s *SVCBAlpn) unpack(b []byte) error {
338	// Estimate the size of the smallest alpn as 4 bytes
339	alpn := make([]string, 0, len(b)/4)
340	for i := 0; i < len(b); {
341		length := int(b[i])
342		i++
343		if i+length > len(b) {
344			return errors.New("dns: svcbalpn: alpn array overflowing")
345		}
346		alpn = append(alpn, string(b[i:i+length]))
347		i += length
348	}
349	s.Alpn = alpn
350	return nil
351}
352
353func (s *SVCBAlpn) parse(b string) error {
354	s.Alpn = strings.Split(b, ",")
355	return nil
356}
357
358func (s *SVCBAlpn) len() int {
359	var l int
360	for _, e := range s.Alpn {
361		l += 1 + len(e)
362	}
363	return l
364}
365
366func (s *SVCBAlpn) copy() SVCBKeyValue {
367	return &SVCBAlpn{
368		append([]string(nil), s.Alpn...),
369	}
370}
371
372// SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
373// Basic use pattern for creating a no-default-alpn option:
374//
375//	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
376//	e := new(dns.SVCBNoDefaultAlpn)
377//	s.Value = append(s.Value, e)
378type SVCBNoDefaultAlpn struct{}
379
380func (*SVCBNoDefaultAlpn) Key() SVCBKey          { return SVCB_NO_DEFAULT_ALPN }
381func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue    { return &SVCBNoDefaultAlpn{} }
382func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil }
383func (*SVCBNoDefaultAlpn) String() string        { return "" }
384func (*SVCBNoDefaultAlpn) len() int              { return 0 }
385
386func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
387	if len(b) != 0 {
388		return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value")
389	}
390	return nil
391}
392
393func (*SVCBNoDefaultAlpn) parse(b string) error {
394	if b != "" {
395		return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value")
396	}
397	return nil
398}
399
400// SVCBPort pair defines the port for connection.
401// Basic use pattern for creating a port option:
402//
403//	s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
404//	e := new(dns.SVCBPort)
405//	e.Port = 80
406//	s.Value = append(s.Value, e)
407type SVCBPort struct {
408	Port uint16
409}
410
411func (*SVCBPort) Key() SVCBKey         { return SVCB_PORT }
412func (*SVCBPort) len() int             { return 2 }
413func (s *SVCBPort) String() string     { return strconv.FormatUint(uint64(s.Port), 10) }
414func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} }
415
416func (s *SVCBPort) unpack(b []byte) error {
417	if len(b) != 2 {
418		return errors.New("dns: svcbport: port length is not exactly 2 octets")
419	}
420	s.Port = binary.BigEndian.Uint16(b)
421	return nil
422}
423
424func (s *SVCBPort) pack() ([]byte, error) {
425	b := make([]byte, 2)
426	binary.BigEndian.PutUint16(b, s.Port)
427	return b, nil
428}
429
430func (s *SVCBPort) parse(b string) error {
431	port, err := strconv.ParseUint(b, 10, 16)
432	if err != nil {
433		return errors.New("dns: svcbport: port out of range")
434	}
435	s.Port = uint16(port)
436	return nil
437}
438
439// SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections
440// if A and AAAA record responses for SVCB's Target domain haven't been received.
441// In that case, optionally, A and AAAA requests can be made, after which the connection
442// to the hinted IP address may be terminated and a new connection may be opened.
443// Basic use pattern for creating an ipv4hint option:
444//
445//	h := new(dns.HTTPS)
446//	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
447//	e := new(dns.SVCBIPv4Hint)
448//	e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()}
449//
450//  Or
451//
452//	e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()}
453//	h.Value = append(h.Value, e)
454type SVCBIPv4Hint struct {
455	Hint []net.IP
456}
457
458func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT }
459func (s *SVCBIPv4Hint) len() int   { return 4 * len(s.Hint) }
460
461func (s *SVCBIPv4Hint) pack() ([]byte, error) {
462	b := make([]byte, 0, 4*len(s.Hint))
463	for _, e := range s.Hint {
464		x := e.To4()
465		if x == nil {
466			return nil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6")
467		}
468		b = append(b, x...)
469	}
470	return b, nil
471}
472
473func (s *SVCBIPv4Hint) unpack(b []byte) error {
474	if len(b) == 0 || len(b)%4 != 0 {
475		return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4")
476	}
477	x := make([]net.IP, 0, len(b)/4)
478	for i := 0; i < len(b); i += 4 {
479		x = append(x, net.IP(b[i:i+4]))
480	}
481	s.Hint = x
482	return nil
483}
484
485func (s *SVCBIPv4Hint) String() string {
486	str := make([]string, len(s.Hint))
487	for i, e := range s.Hint {
488		x := e.To4()
489		if x == nil {
490			return "<nil>"
491		}
492		str[i] = x.String()
493	}
494	return strings.Join(str, ",")
495}
496
497func (s *SVCBIPv4Hint) parse(b string) error {
498	if strings.Contains(b, ":") {
499		return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
500	}
501	str := strings.Split(b, ",")
502	dst := make([]net.IP, len(str))
503	for i, e := range str {
504		ip := net.ParseIP(e).To4()
505		if ip == nil {
506			return errors.New("dns: svcbipv4hint: bad ip")
507		}
508		dst[i] = ip
509	}
510	s.Hint = dst
511	return nil
512}
513
514func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
515	hint := make([]net.IP, len(s.Hint))
516	for i, ip := range s.Hint {
517		hint[i] = copyIP(ip)
518	}
519
520	return &SVCBIPv4Hint{
521		Hint: hint,
522	}
523}
524
525// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
526// Basic use pattern for creating an echconfig option:
527//
528//	h := new(dns.HTTPS)
529//	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
530//	e := new(dns.SVCBECHConfig)
531//	e.ECH = []byte{0xfe, 0x08, ...}
532//	h.Value = append(h.Value, e)
533type SVCBECHConfig struct {
534	ECH []byte
535}
536
537func (*SVCBECHConfig) Key() SVCBKey     { return SVCB_ECHCONFIG }
538func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) }
539func (s *SVCBECHConfig) len() int       { return len(s.ECH) }
540
541func (s *SVCBECHConfig) pack() ([]byte, error) {
542	return append([]byte(nil), s.ECH...), nil
543}
544
545func (s *SVCBECHConfig) copy() SVCBKeyValue {
546	return &SVCBECHConfig{
547		append([]byte(nil), s.ECH...),
548	}
549}
550
551func (s *SVCBECHConfig) unpack(b []byte) error {
552	s.ECH = append([]byte(nil), b...)
553	return nil
554}
555func (s *SVCBECHConfig) parse(b string) error {
556	x, err := fromBase64([]byte(b))
557	if err != nil {
558		return errors.New("dns: svcbechconfig: bad base64 echconfig")
559	}
560	s.ECH = x
561	return nil
562}
563
564// SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections
565// if A and AAAA record responses for SVCB's Target domain haven't been received.
566// In that case, optionally, A and AAAA requests can be made, after which the
567// connection to the hinted IP address may be terminated and a new connection may be opened.
568// Basic use pattern for creating an ipv6hint option:
569//
570//	h := new(dns.HTTPS)
571//	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
572//	e := new(dns.SVCBIPv6Hint)
573//	e.Hint = []net.IP{net.ParseIP("2001:db8::1")}
574//	h.Value = append(h.Value, e)
575type SVCBIPv6Hint struct {
576	Hint []net.IP
577}
578
579func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT }
580func (s *SVCBIPv6Hint) len() int   { return 16 * len(s.Hint) }
581
582func (s *SVCBIPv6Hint) pack() ([]byte, error) {
583	b := make([]byte, 0, 16*len(s.Hint))
584	for _, e := range s.Hint {
585		if len(e) != net.IPv6len || e.To4() != nil {
586			return nil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4")
587		}
588		b = append(b, e...)
589	}
590	return b, nil
591}
592
593func (s *SVCBIPv6Hint) unpack(b []byte) error {
594	if len(b) == 0 || len(b)%16 != 0 {
595		return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16")
596	}
597	x := make([]net.IP, 0, len(b)/16)
598	for i := 0; i < len(b); i += 16 {
599		ip := net.IP(b[i : i+16])
600		if ip.To4() != nil {
601			return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
602		}
603		x = append(x, ip)
604	}
605	s.Hint = x
606	return nil
607}
608
609func (s *SVCBIPv6Hint) String() string {
610	str := make([]string, len(s.Hint))
611	for i, e := range s.Hint {
612		if x := e.To4(); x != nil {
613			return "<nil>"
614		}
615		str[i] = e.String()
616	}
617	return strings.Join(str, ",")
618}
619
620func (s *SVCBIPv6Hint) parse(b string) error {
621	if strings.Contains(b, ".") {
622		return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
623	}
624	str := strings.Split(b, ",")
625	dst := make([]net.IP, len(str))
626	for i, e := range str {
627		ip := net.ParseIP(e)
628		if ip == nil {
629			return errors.New("dns: svcbipv6hint: bad ip")
630		}
631		dst[i] = ip
632	}
633	s.Hint = dst
634	return nil
635}
636
637func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
638	hint := make([]net.IP, len(s.Hint))
639	for i, ip := range s.Hint {
640		hint[i] = copyIP(ip)
641	}
642
643	return &SVCBIPv6Hint{
644		Hint: hint,
645	}
646}
647
648// SVCBLocal pair is intended for experimental/private use. The key is recommended
649// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
650// Basic use pattern for creating a keyNNNNN option:
651//
652//	h := new(dns.HTTPS)
653//	h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
654//	e := new(dns.SVCBLocal)
655//	e.KeyCode = 65400
656//	e.Data = []byte("abc")
657//	h.Value = append(h.Value, e)
658type SVCBLocal struct {
659	KeyCode SVCBKey // Never 65535 or any assigned keys.
660	Data    []byte  // All byte sequences are allowed.
661}
662
663func (s *SVCBLocal) Key() SVCBKey          { return s.KeyCode }
664func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
665func (s *SVCBLocal) len() int              { return len(s.Data) }
666
667func (s *SVCBLocal) unpack(b []byte) error {
668	s.Data = append([]byte(nil), b...)
669	return nil
670}
671
672func (s *SVCBLocal) String() string {
673	var str strings.Builder
674	str.Grow(4 * len(s.Data))
675	for _, e := range s.Data {
676		if ' ' <= e && e <= '~' {
677			switch e {
678			case '"', ';', ' ', '\\':
679				str.WriteByte('\\')
680				str.WriteByte(e)
681			default:
682				str.WriteByte(e)
683			}
684		} else {
685			str.WriteString(escapeByte(e))
686		}
687	}
688	return str.String()
689}
690
691func (s *SVCBLocal) parse(b string) error {
692	data := make([]byte, 0, len(b))
693	for i := 0; i < len(b); {
694		if b[i] != '\\' {
695			data = append(data, b[i])
696			i++
697			continue
698		}
699		if i+1 == len(b) {
700			return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated")
701		}
702		if isDigit(b[i+1]) {
703			if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
704				a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
705				if err == nil {
706					i += 4
707					data = append(data, byte(a))
708					continue
709				}
710			}
711			return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet")
712		} else {
713			data = append(data, b[i+1])
714			i += 2
715		}
716	}
717	s.Data = data
718	return nil
719}
720
721func (s *SVCBLocal) copy() SVCBKeyValue {
722	return &SVCBLocal{s.KeyCode,
723		append([]byte(nil), s.Data...),
724	}
725}
726
727func (rr *SVCB) String() string {
728	s := rr.Hdr.String() +
729		strconv.Itoa(int(rr.Priority)) + " " +
730		sprintName(rr.Target)
731	for _, e := range rr.Value {
732		s += " " + e.Key().String() + "=\"" + e.String() + "\""
733	}
734	return s
735}
736
737// areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their
738// copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function.
739func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
740	a = append([]SVCBKeyValue(nil), a...)
741	b = append([]SVCBKeyValue(nil), b...)
742	sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() })
743	sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() })
744	for i, e := range a {
745		if e.Key() != b[i].Key() {
746			return false
747		}
748		b1, err1 := e.pack()
749		b2, err2 := b[i].pack()
750		if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) {
751			return false
752		}
753	}
754	return true
755}
756