// Package extension implements the extension values in the ClientHello/ServerHello
package extension

import "encoding/binary"

// TypeValue is the 2 byte value for a TLS Extension as registered in the IANA
//
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
type TypeValue uint16

// TypeValue constants
const (
	ServerNameTypeValue                   TypeValue = 0
	SupportedEllipticCurvesTypeValue      TypeValue = 10
	SupportedPointFormatsTypeValue        TypeValue = 11
	SupportedSignatureAlgorithmsTypeValue TypeValue = 13
	UseSRTPTypeValue                      TypeValue = 14
	UseExtendedMasterSecretTypeValue      TypeValue = 23
	RenegotiationInfoTypeValue            TypeValue = 65281
)

// Extension represents a single TLS extension
type Extension interface {
	Marshal() ([]byte, error)
	Unmarshal(data []byte) error
	TypeValue() TypeValue
}

// Unmarshal many extensions at once
func Unmarshal(buf []byte) ([]Extension, error) {
	switch {
	case len(buf) == 0:
		return []Extension{}, nil
	case len(buf) < 2:
		return nil, errBufferTooSmall
	}

	declaredLen := binary.BigEndian.Uint16(buf)
	if len(buf)-2 != int(declaredLen) {
		return nil, errLengthMismatch
	}

	extensions := []Extension{}
	unmarshalAndAppend := func(data []byte, e Extension) error {
		err := e.Unmarshal(data)
		if err != nil {
			return err
		}
		extensions = append(extensions, e)
		return nil
	}

	for offset := 2; offset < len(buf); {
		if len(buf) < (offset + 2) {
			return nil, errBufferTooSmall
		}
		var err error
		switch TypeValue(binary.BigEndian.Uint16(buf[offset:])) {
		case ServerNameTypeValue:
			err = unmarshalAndAppend(buf[offset:], &ServerName{})
		case SupportedEllipticCurvesTypeValue:
			err = unmarshalAndAppend(buf[offset:], &SupportedEllipticCurves{})
		case UseSRTPTypeValue:
			err = unmarshalAndAppend(buf[offset:], &UseSRTP{})
		case UseExtendedMasterSecretTypeValue:
			err = unmarshalAndAppend(buf[offset:], &UseExtendedMasterSecret{})
		case RenegotiationInfoTypeValue:
			err = unmarshalAndAppend(buf[offset:], &RenegotiationInfo{})
		default:
		}
		if err != nil {
			return nil, err
		}
		if len(buf) < (offset + 4) {
			return nil, errBufferTooSmall
		}
		extensionLength := binary.BigEndian.Uint16(buf[offset+2:])
		offset += (4 + int(extensionLength))
	}
	return extensions, nil
}

// Marshal many extensions at once
func Marshal(e []Extension) ([]byte, error) {
	extensions := []byte{}
	for _, e := range e {
		raw, err := e.Marshal()
		if err != nil {
			return nil, err
		}
		extensions = append(extensions, raw...)
	}
	out := []byte{0x00, 0x00}
	binary.BigEndian.PutUint16(out, uint16(len(extensions)))
	return append(out, extensions...), nil
}
