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