// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dnsmessage

import (
	
)

// An SVCBResource is an SVCB Resource record.
type SVCBResource struct {
	Priority uint16
	Target   Name
	Params   []SVCParam // Must be in strict increasing order by Key.
}

func ( *SVCBResource) () Type {
	return TypeSVCB
}

// GoString implements fmt.GoStringer.GoString.
func ( *SVCBResource) () string {
	 := []byte("dnsmessage.SVCBResource{" +
		"Priority: " + printUint16(.Priority) + ", " +
		"Target: " + .Target.GoString() + ", " +
		"Params: []dnsmessage.SVCParam{")
	if len(.Params) > 0 {
		 = append(, .Params[0].GoString()...)
		for ,  := range .Params[1:] {
			 = append(, ", "+.GoString()...)
		}
	}
	 = append(, "}}"...)
	return string()
}

// An HTTPSResource is an HTTPS Resource record.
// It has the same format as the SVCB record.
type HTTPSResource struct {
	// Alias for SVCB resource record.
	SVCBResource
}

func ( *HTTPSResource) () Type {
	return TypeHTTPS
}

// GoString implements fmt.GoStringer.GoString.
func ( *HTTPSResource) () string {
	return "dnsmessage.HTTPSResource{SVCBResource: " + .SVCBResource.GoString() + "}"
}

// GetParam returns a parameter value by key.
func ( *SVCBResource) ( SVCParamKey) ( []byte,  bool) {
	for  := range .Params {
		if .Params[].Key ==  {
			return .Params[].Value, true
		}
		if .Params[].Key >  {
			break
		}
	}
	return nil, false
}

// SetParam sets a parameter value by key.
// The Params list is kept sorted by key.
func ( *SVCBResource) ( SVCParamKey,  []byte) {
	 := 0
	for  < len(.Params) {
		if .Params[].Key >=  {
			break
		}
		++
	}

	if  < len(.Params) && .Params[].Key ==  {
		.Params[].Value = 
		return
	}

	.Params = slices.Insert(.Params, , SVCParam{Key: , Value: })
}

// DeleteParam deletes a parameter by key.
// It returns true if the parameter was present.
func ( *SVCBResource) ( SVCParamKey) bool {
	for  := range .Params {
		if .Params[].Key ==  {
			.Params = slices.Delete(.Params, , +1)
			return true
		}
		if .Params[].Key >  {
			break
		}
	}
	return false
}

// A SVCParam is a service parameter.
type SVCParam struct {
	Key   SVCParamKey
	Value []byte
}

// GoString implements fmt.GoStringer.GoString.
func ( SVCParam) () string {
	return "dnsmessage.SVCParam{" +
		"Key: " + .Key.GoString() + ", " +
		"Value: []byte{" + printByteSlice(.Value) + "}}"
}

// A SVCParamKey is a key for a service parameter.
type SVCParamKey uint16

// Values defined at https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys.
const (
	SVCParamMandatory          SVCParamKey = 0
	SVCParamALPN               SVCParamKey = 1
	SVCParamNoDefaultALPN      SVCParamKey = 2
	SVCParamPort               SVCParamKey = 3
	SVCParamIPv4Hint           SVCParamKey = 4
	SVCParamECH                SVCParamKey = 5
	SVCParamIPv6Hint           SVCParamKey = 6
	SVCParamDOHPath            SVCParamKey = 7
	SVCParamOHTTP              SVCParamKey = 8
	SVCParamTLSSupportedGroups SVCParamKey = 9
)

var svcParamKeyNames = map[SVCParamKey]string{
	SVCParamMandatory:          "Mandatory",
	SVCParamALPN:               "ALPN",
	SVCParamNoDefaultALPN:      "NoDefaultALPN",
	SVCParamPort:               "Port",
	SVCParamIPv4Hint:           "IPv4Hint",
	SVCParamECH:                "ECH",
	SVCParamIPv6Hint:           "IPv6Hint",
	SVCParamDOHPath:            "DOHPath",
	SVCParamOHTTP:              "OHTTP",
	SVCParamTLSSupportedGroups: "TLSSupportedGroups",
}

// String implements fmt.Stringer.String.
func ( SVCParamKey) () string {
	if ,  := svcParamKeyNames[];  {
		return 
	}
	return printUint16(uint16())
}

// GoString implements fmt.GoStringer.GoString.
func ( SVCParamKey) () string {
	if ,  := svcParamKeyNames[];  {
		return "dnsmessage.SVCParam" + 
	}
	return printUint16(uint16())
}

func ( *SVCBResource) ( []byte,  map[string]uint16,  int) ([]byte, error) {
	 := 
	 = packUint16(, .Priority)
	// https://datatracker.ietf.org/doc/html/rfc3597#section-4 prohibits name
	// compression for RR types that are not "well-known".
	// https://datatracker.ietf.org/doc/html/rfc9460#section-2.2 explicitly states that
	// compression of the Target is prohibited, following RFC 3597.
	,  := .Target.pack(, nil, 0)
	if  != nil {
		return , &nestedError{"SVCBResource.Target", }
	}
	var  SVCParamKey
	for ,  := range .Params {
		if  > 0 && .Key <=  {
			return , &nestedError{"SVCBResource.Params", errParamOutOfOrder}
		}
		if len(.Value) > (1<<16)-1 {
			return , &nestedError{"SVCBResource.Params", errTooLongSVCBValue}
		}
		 = packUint16(, uint16(.Key))
		 = packUint16(, uint16(len(.Value)))
		 = append(, .Value...)
	}
	return , nil
}

func unpackSVCBResource( []byte,  int,  uint16) (SVCBResource, error) {
	// Wire format reference: https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2.
	 := SVCBResource{}
	 := 
	 :=  + int()

	var  error
	if .Priority, ,  = unpackUint16(, );  != nil {
		return SVCBResource{}, &nestedError{"Priority", }
	}

	if ,  = .Target.unpack(, );  != nil {
		return SVCBResource{}, &nestedError{"Target", }
	}

	// Two-pass parsing to avoid allocations.
	// First, count the number of params.
	 := 0
	var  uint16
	 = 
	var  uint16
	for  <  {
		var ,  uint16
		if , ,  = unpackUint16(, );  != nil {
			return SVCBResource{}, &nestedError{"Params key", }
		}
		if  > 0 &&  <=  {
			// As per https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2, clients MUST
			// consider the RR malformed if the SvcParamKeys are not in strictly increasing numeric order
			return SVCBResource{}, &nestedError{"Params", errParamOutOfOrder}
		}
		if , ,  = unpackUint16(, );  != nil {
			return SVCBResource{}, &nestedError{"Params value length", }
		}
		if +int() >  {
			return SVCBResource{}, errResourceLen
		}
		 += 
		 += int()
		++
	}
	if  !=  {
		return SVCBResource{}, errResourceLen
	}

	// Second, fill in the params.
	.Params = make([]SVCParam, )
	// valuesBuf is used to hold all param values to reduce allocations.
	// Each param's Value slice will point into this buffer.
	 := make([]byte, )
	 = 
	for  := 0;  < ; ++ {
		 := &.Params[]
		var ,  uint16
		if , ,  = unpackUint16(, );  != nil {
			return SVCBResource{}, &nestedError{"param key", }
		}
		.Key = SVCParamKey()
		if , ,  = unpackUint16(, );  != nil {
			return SVCBResource{}, &nestedError{"param length", }
		}
		if copy(, [:+int()]) != int() {
			return SVCBResource{}, &nestedError{"param value", errCalcLen}
		}
		.Value = [::]
		 = [:]
		 += int()
	}

	return , nil
}

// genericSVCBResource parses a single Resource Record compatible with SVCB.
func ( *Parser) ( Type) (SVCBResource, error) {
	if !.resHeaderValid || .resHeaderType !=  {
		return SVCBResource{}, ErrNotStarted
	}
	,  := unpackSVCBResource(.msg, .off, .resHeaderLength)
	if  != nil {
		return SVCBResource{}, 
	}
	.off += int(.resHeaderLength)
	.resHeaderValid = false
	.index++
	return , nil
}

// SVCBResource parses a single SVCBResource.
//
// One of the XXXHeader methods must have been called before calling this
// method.
func ( *Parser) () (SVCBResource, error) {
	return .genericSVCBResource(TypeSVCB)
}

// HTTPSResource parses a single HTTPSResource.
//
// One of the XXXHeader methods must have been called before calling this
// method.
func ( *Parser) () (HTTPSResource, error) {
	,  := .genericSVCBResource(TypeHTTPS)
	if  != nil {
		return HTTPSResource{}, 
	}
	return HTTPSResource{}, nil
}

// genericSVCBResource is the generic implementation for adding SVCB-like resources.
func ( *Builder) ( ResourceHeader,  SVCBResource) error {
	if  := .checkResourceSection();  != nil {
		return 
	}
	, ,  := .pack(.msg, .compression, .start)
	if  != nil {
		return &nestedError{"ResourceHeader", }
	}
	 := len()
	if ,  = .pack(, .compression, .start);  != nil {
		return &nestedError{"ResourceBody", }
	}
	if  := .fixLen(, , );  != nil {
		return 
	}
	if  := .incrementSectionCount();  != nil {
		return 
	}
	.msg = 
	return nil
}

// SVCBResource adds a single SVCBResource.
func ( *Builder) ( ResourceHeader,  SVCBResource) error {
	.Type = .realType()
	return .genericSVCBResource(, )
}

// HTTPSResource adds a single HTTPSResource.
func ( *Builder) ( ResourceHeader,  HTTPSResource) error {
	.Type = .realType()
	return .genericSVCBResource(, .SVCBResource)
}